1 unstable release

0.1.0 Aug 12, 2021

#434 in Authentication

24 downloads per month

MIT/Apache

44KB
456 lines

tide-openidconnect

Build Status Latest version Documentation License Contributor Covenant

OpenID Connect middleware for Tide.

This crate provides middleware that can be used to authenticate the users of a Tide application, optionally preventing access to certain routes unless the request has been authenticated. The middleware does not interact with, store, or otherwise process user credentials, but instead relies on an OpenID Connect Identity Provider to authenticate the user.

Identity Providers manage user credentials, may or may not allow user sign ups, and use the OAuth 2.0 framework to allow applications to request tokens. Those tokens can then be used to identify users and grant them access to the application. This crate provides the functionality necessary to integrate with Identity Providers, perform token exchanges, and make the results available to Tide requests.

Auth0, Azure, and Google are just a few examples of OpenID Connect-compatible identity providers

Usage

Add this to your Cargo.toml:

[dependencies]
tide-openidconnect = "0.1"

Example

use tide_openidconnect::{OpenIdConnectRequestExt, OpenIdConnectRouteExt};

#[async_std::main]
async fn main() -> tide::Result<()> {
    let mut app = tide::new();

    // OpenID Connect middleware *requires* session storage. Note that the
    // cookies must be configured with `SameSite::Lax`.
    app.with(
        tide::sessions::SessionMiddleware::new(
            tide::sessions::MemoryStore::new(),
            b"don't actually use a hardcoded secret",
        )
        .with_same_site_policy(tide::http::cookies::SameSite::Lax),
    );

    // Initialize the OpenID Connect middleware; normally all of these
    // configuration values would come from an environment-specific config
    // file, and in fact that is why they are in their own struct (so that
    // you can deserialize that struct from a file or environment variables).
    app.with(
        tide_openidconnect::OpenIdConnectMiddleware::new(
            &tide_openidconnect::Config {
                issuer_url: tide_openidconnect::IssuerUrl::new("https://your-tenant-name.us.auth0.com/".to_string()).unwrap(),
                client_id: tide_openidconnect::ClientId::new("app-id-goes-here".to_string()),
                client_secret: tide_openidconnect::ClientSecret::new("app-secret-goes-here".to_string()),
                redirect_url: tide_openidconnect::RedirectUrl::new("http://your.cool.site/callback".to_string()).unwrap(),
                idp_logout_url: None,
            }
        )
        .await,
    );

    // Define a basic Tide route, but protect it with the middleware's
    // `.authenticated()` route extension.
    app.at("/").authenticated().get(|req: tide::Request<()>| async move {
        Ok(format!(
            "If you got this far, then the request is authenticated, and your user id is {:?}",
            req.user_id()
        ))
    });

    app.listen("127.0.0.1:8000").await?;
    Ok(())
}

See more examples in the examples directory.

Login Flow

This middleware uses the OAuth 2.0 Authorization Code Grant flow, which is a redirection-based flow: the user navigates to the login path (/login by default) and then the middleware redirects the browser to the Identity Provider's authorization endpoint. After a successful sign in, the browser will be redirected to the login landing path.

One way to initiate this process is to check the authentication status of each request using the is_authenticated() request extension and then display a link to the login path if the request is not authenticated.

This crate also provides a route extension that can force unauthenticated requests to go through the login process. This is an easy way to protect your routes without requiring that they individually check the request's authentication status. Using this route extension, an unauthenticated request will automatically redirect the browser to the login path, and then to the Identity Provider.

Note that using this route extension comes with certain caveats; see the OpenIdConnectRouteExt docs for more information.

Logout Flow

Users can log out of the application by navigating to the logout path (which defaults to /logout); this destroys their entire Tide session and returns them to the logout landing path. You can optionally configure the logout process to only clear the authentication state from the session, leaving the remainder of the session data intact.

Some Identity Providers also support clearing the browser state related to the provider, and your application can optionally enable that functionality by setting the idp_logout_url when configuring the middleware.

Tide Route Interception

There are three routes used by this middleware in order to perform the functions of the OAuth 2.0 flow. Here are the default values for these paths, two of which have already been described above, and all of which can be changed:

  • /login -- Initiates the OAuth 2.0 login process.
  • /logout -- Destroys the session state and optionally clears the Identity Provider's state as well.
  • /callback -- The "Redirect URL" to which the Identity Provider will send the browser after a successful sign in.

You do not have to define these routes in your Tide server; the middleware intercepts GET requests to those paths and handles them on its own. Because of this behavior, those paths are not available for use in your application.

Session Middleware Requirements

The primary output of the OpenID Connect middleware is to augment the Tide request object with information about the authentication state of the request. Tide's session middleware is used to track that state, and so the OpenID Connect middleware requires that you install and configure session middleware in your Tide application. This middleware will panic on the first login request if the session middleware is not present.

Furthermore, because of the various HTTP redirects in the OAuth 2.0 flow, the session cookie needs to be configured with the SameSite::Lax security policy. This is safe as long as your GET (and HEAD, OPTIONS, and TRACE) requests do not mutate state or perform side-effecting operations as part of processing the request. Protecting requests to "safe" (read-only) HTTP methods that are not actually safe is the primary reason to use the default session cookie policy of SameSite::Strict. Attacks that target unsafe HTTP APIs using cookies are known as Cross-Site Request Forgery (CSRF) attacks, and SameSite::Strict is one of the easiest ways to defend against those attacks, but SameSite::Lax is safe to use as long as your GET (and HEAD and ...) requests are exclusively tasked with returning data.

Note that the OpenID Connect middleware does mutate auth state as part of processing OAuth 2.0 GET requests, but it also implements multiple levels of CSRF protection in order to protect those GET requests from malicious attacks.

Conduct

This project adheres to the Contributor Covenant Code of Conduct. This describes the minimum behavior expected from all contributors.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~35–49MB
~1M SLoC