A Rocket App with Auth0 Integration

In this post we’ll be walking through an example application I’ve sketched out on GitHub. I threw this together because I didn’t see anything similar for Rust, Rocket, and basic Auth0 integration.

We will go through the process step by step, from configuring Auth0 in their management console, to building, configuring, and running our application.

Auth0 Configuration

This example uses what Auth0 calls a “Regular Web Application”. Create one of these in the management console.

auth0 settings

Create a new application, or use an existing one. Our application will need these application settings values, so keep them handy:

Since our application will do an authentication handshake with Auth0, we must also enter some configuration into the Auth0 management console itself. This ensures Auth0 will only accept and call back to URLs we explicitly specify (it’s all about security, after all).

First, add our localhost URL to the list of Allowed Callback URLs.

https://localhost:8000/login

Scroll down and save your changes.

You can also take note of the values in Advanced Settings > Endpoints. Auth0 sets these up for us, and our application will make use of these. Note that your Auth0 domain is a component of these endpoints.

Install Rust (nightly)

You’ll need to install Rust, if you haven’t already.

Rust comes in two main flavors, stable and nightly. Since our project dependencies (including Rocket) require bleeding-edge compiler features, we must use nightly. Don’t let that scare you, managing versions of Rust is easy with rustup.

First, get rustup from https://rustup.rs/ and then use it to install and use a nightly Rust compiler by default.

rustup default nightly

You should now have Rust and cargo, the official Rust build tool and package manager. You can think of cargo as npm or pip, but for Rust.

Build the Rust Example

Clone our example project to wherever you like to write code.

git clone https://github.com/anxiousmodernman/auth0-rocket-rust-example
cd auth0-rocket-rust-example

Use cargo to build our project. If this is your first time using Rust, compilation will take a long time. But don’t worry! Build artifacts are cached, so subsequent builds will be much faster.

cargo build

Generate Self-signed Certificates

In our example, we provide a script to generate self-signed certificates to serve our Rocket app locally over TLS. The script requires a working Go installation. Run it like this:

./generate-cert.sh

Configure and Run the Rust Example

Almost there. We need to provide our application the Auth0 configuration values mentioned earlier. We’ll use 1) a Rocket.toml file and 2) an environment variable.

First, make a copy of the example Rocket.toml file.

cp Rocket.toml.example Rocket.toml

In Rocket.toml add your Auth0 client id, domain, and the localhost redirect uri to the [global] configuration stanza. Be sure to quote the strings.

[global]
client_id = "YOUR_AUTH0_CLIENT_ID"
redirect_uri = "http://localhost:8000/login"
auth0_domain = "YOUR_AUTH0_DOMAIN"

## only if you want to use TLS
[global.tls]
certs = "cert.pem"
key = "key.pem"

Again, note that here we are assuming you’re using TLS certs generated earlier.

Next, set AUTH0_CLIENT_SECRET in your shell environment.

export AUTH0_CLIENT_SECRET="very-secret-value"

We’re ready to run our app! Execute cargo run and open a browser at http://localhost:8000/ You will be redirected to the login link.

picture of login link

Click the link, and you’ll be redirected to an Auth0-hosted login page. Only valid users of your app can login here. Upon successful login, you’ll be redirected back to localhost.

picture of guarded route

Code Walkthrough

Great, it works! But how?

Routes in Rocket

Rocket uses custom attributes to specify it’s routes. To me, they resemble the route decorators from Flask. Like Flask, Rocket routes are just rust functions.

Here we see the #[get("/login")] attribute decorating the login function. This will handle GET requests to the /login route.

/// Our login link.
#[get("/login")]
fn login() -> Markup { 
    html!{
        head {
            title "Login | Auth0 Rocket Example"
            link rel="stylesheet" href="static/css/style.css";
        }
        body {
            a class="login" href="/auth0" "Login With Auth0!"
        }
    }
}

These custom attributes are a bleeding-edge feature in Rust, and one of the reasons we need the nightly compiler to use Rocket.

The Markup type and the excited!-looking html! macro are from maud, a library that allows us to write dynamic templates as a DSL directly in our Rust code. There isn’t much else to say about maud. The DSL is very HTML-like. Its integration with Rocket is really great, and we don’t have to ship any static HTML files. It’s all generated at compile time.

Rocket’s Request Guards

This is a tutorial about protecting parts of our site from unauthenticated users. Rocket lets us do this with request guards. A request guard in Rocket is simply a Rust type that implements the FromRequest trait. We can then use these types in the function signature of our routes. The FromRequest code, along with the url, will determine whether the route is served.

In our example, we have two handlers for the same "/" route, but they take different parameters. Crucially, our User type is a request guard.

#[get("/")]
fn home(user: User) -> Markup { ... }

fn home_redirect() -> Redirect {
    Redirect::to("/login")
}

Since our User type is a request guard, the home function will only be routed to if the associated request guard code succeeds.

Under the hood, our implementation of this request guard checks the user’s cookie for a hashed jwt, and ensures that cookie value matches a hashed jwt in our database. If any part of this fails, home will not be evaluated, and the request will be forwarded to the next matching route. In our example, that’s the home_redirect function, which immediately redirects to our login link.

Other frameworks use HTTP middleware for this sort of thing. In Rocket, we’re encouraged to use request guards. This is a nice feature, because we could potentially specify many request guards for a route, and adding or removing them is a simpler process than refactoring middleware.

Rocket’s Managed State

Routes need to make use of shared resources, like database connections. Rocket requires that we explicitly declare these dependencies with the manage method at launch. Here is a snippet of code from our main function where we open a sled database and bring it under Rocket’s state management.

let db: DB = {
    let config = sled::ConfigBuilder::new().path(".data").build();
    Arc::new(sled::Tree::start(config).unwrap())
};
rocket::ignite().mount(...).manage(db).attach(...).launch();
                        //  ^ call to manage here

Note: an Arc in Rust is a smart pointer that lets us share resources between threads. Our webserver is multi-threaded, so we must wrap our db handle with an Arc.

Once a resource is managed, we can bring it into the scope of any of our routes by adding its type, wrapped with State, to the parameters of a handler function. When Auth0 calls us back during an authentication handshake, we need access to two bits of shared state: the database pointer and a settings object we construct at launch.

#[get("/callback?<callback_params>")]
fn auth0_callback(
    callback_params: CallbackParams,
    mut cookies: Cookies,
    db: State<DB>,
    settings: State<AuthSettings>,
) -> Result<Redirect, Status> { ... }

In the auth0_callback function, both db and settings are passed into our function’s scope automatically. We can use these resources safely for the duration of our function.

Conclusion

Many docs are linked from the README of this example project’s repo, all from the Auth0 community.

If you’re new to Rust, jumping straight into web servers and authentication can be daunting. I hope that I’ve provided something that builds, runs, and gets you started. If anything is broken, or if you have better security recommendations please open an issue.