Learn how to implement authentication using Rust.
cargo install cargo-shuttle
. We will first go to a new directory for our project and create a new Axum app with shuttle init --template axum
.
You should see the following in src/main.rs
:
cargo add tera
. We will store all our
templates
in a template directory in the project root.
We want a general layout for our site, so we create a base layout. In our base
layout, we can add specific tags that will apply to all pages such as a
Google font. With this layout all the content will
be injected in place of {% block content %}{% endblock content %}
:
/
path
include_str!
macro so
that the content is in the binary. This way we don’t have to deal with the
complexities of a filesystem at runtime. We register both templates so that the
index
page knows about base.html
.
Arc
so that extension cloning does not deep clone all the templates)
public/styles.css
file
include_str!
to not have to worry about the filesystem
at runtime.
ServeDiris
an alternative if you have a filesystem at runtime. You can use this method for
other static assets like JavaScript and favicons.
shuttle run
.
Nice!
id
is generated by the database using a sequence. The id
is a primary
key, which we will use to reference users. It is better to use a fixed value
field for identification rather than using something like the username
field
because you may add the ability to change usernames, which can leave things
pointing to the wrong places.
cargo add sqlx -F postgres runtime-tokio-native-tls
.
We will also enable the Postgres feature for Shuttle with
cargo add shuttle-service -F sqlx-postgres
.
Now back in the code we add a parameter with
#[shuttle_shared_db::Postgres] pool: Database
. The
#[shuttle_shared_db::Postgres]
annotation tells shuttle to provision a
Postgres database using the
infrastructure from code design.
enctype
being multipart, which matches what we are parsing in the
handler. The above has a few attributes to do some client-side validation, but
in the full demo it is also handled on the server.
We create a handler for this markup in the same way as done for our index with:
signup
to the Tera instance and then add both the get and post
handlers to the router by adding it to the chain:
schema.sql
:
.to_string()
. We will send a response that
does two things, sets this new value and returns/redirects us back to the index
page. We will create a utility function for doing this:
AuthState
which makes the database request.
Now we have the user’s token we need to get their information. We can do that
using SQL joins
Max-Age
set to 0.
shuttle deploy
Because of our #[shuttle_runtime::main]
annotation and out-the-box Axum
support our deployment doesn’t need any prior config, it is instantly live!
Now you can go ahead with these concepts and add functionality for listing and
deleting users.
The full demo implements these if you are looking for clues.
src/main.rs
. Its API is easy to use and is
well documented. However, it is quite a simple system. I had a few errors where
I would rename or remove templates and because the template picker for rendering
uses a map it can panic at runtime if the template does not exist. It would be
nice if the system allowed checking that templates exist at compile time. The
data sending works on serde serialization, which is a little bit more
computation overhead than I would like. It also does not support streaming. With
streaming, we could send a chunk of HTML that doesn’t depend on database values
first, and then we can add more content when the database transaction has gone
through. If it supported streaming we could avoid the all-or-nothing pages with
white page pauses and start connections to services like Google Fonts earlier.
Let me know what your favorite templating engine is for Rust and whether it
supports those features!
For working with the database, sqlx has typed macros. I didn’t use them here but
for more complex queries you might prefer the type-checking behavior. Maybe 16
bytes for storing session tokens is a bit overkill. You also might want to try
sharding that table if you have a lot of sessions or using a key-value store
(such as Redis) might be simpler. We also didn’t implement cleaning up the
sessions table, if you were storing sessions using Redis you could use the
EXPIRE commandto automatically remove old
keys.