#jwk #jwt #openid-connect #authorization #jwks #axum

jwt-authorizer

jwt authorizer middleware for axum and tonic

18 releases (breaking)

0.15.0 Aug 27, 2024
0.14.0 Jan 22, 2024
0.13.0 Nov 21, 2023
0.10.1 Jul 11, 2023
0.8.1 Mar 16, 2023

#73 in Authentication

Download history 1787/week @ 2024-07-18 2316/week @ 2024-07-25 2806/week @ 2024-08-01 2444/week @ 2024-08-08 1847/week @ 2024-08-15 1824/week @ 2024-08-22 2260/week @ 2024-08-29 3336/week @ 2024-09-05 1666/week @ 2024-09-12 2085/week @ 2024-09-19 2425/week @ 2024-09-26 2116/week @ 2024-10-03 2492/week @ 2024-10-10 2103/week @ 2024-10-17 2644/week @ 2024-10-24 2085/week @ 2024-10-31

9,765 downloads per month
Used in 3 crates

MIT license

84KB
2K SLoC

jwt-authorizer

JWT authoriser Layer for Axum and Tonic.

Features

  • JWT token verification (Bearer)
    • Algoritms: ECDSA, RSA, EdDSA, HMAC
  • JWKS endpoint support
    • Configurable refresh
    • OpenId Connect Discovery
  • Validation
    • exp, nbf, iss, aud
  • Claims extraction
  • Claims checker
  • Tracing support (error logging)
  • tonic support
  • multiple authorizers

Usage Example

# use jwt_authorizer::{AuthError, Authorizer, JwtAuthorizer, JwtClaims, RegisteredClaims, IntoLayer};
# use axum::{routing::get, Router};
# use serde::Deserialize;
# use tokio::net::TcpListener;
# async {

    // let's create an authorizer builder from a JWKS Endpoint
    // (a serializable struct can be used to represent jwt claims, JwtAuthorizer<RegisteredClaims> is the default)
    let auth: Authorizer =
                    JwtAuthorizer::from_jwks_url("http://localhost:3000/oidc/jwks").build().await.unwrap();

    // adding the authorization layer
    let app = Router::new().route("/protected", get(protected))
            .layer(auth.into_layer());

    // proteced handler with user injection (mapping some jwt claims)
    async fn protected(JwtClaims(user): JwtClaims<RegisteredClaims>) -> Result<String, AuthError> {
        // Send the protected data to the user
        Ok(format!("Welcome: {:?}", user.sub))
    }
    let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app.into_make_service()).await.expect("server failed");
# };

Multiple Authorizers

A layer can be built using multiple authorizers (IntoLayer is implemented for [Authorizer<C>; N] and for Vec<Authorizer<C>>). The authorizers are sequentially applied until one of them validates the token. If no authorizer validates it the request is rejected.

Validation

Validation configuration object.

If no validation configuration is provided default values will be applyed.

docs: jwt-authorizer::Validation

# use jwt_authorizer::{JwtAuthorizer, Validation};
# use serde_json::Value;

let validation = Validation::new()
                    .iss(&["https://issuer1", "https://issuer2"])
                    .aud(&["audience1"])
                    .nbf(true)
                    .leeway(20);

let jwt_auth: JwtAuthorizer<Value> = JwtAuthorizer::from_oidc("https://accounts.google.com")
                      .validation(validation);

ClaimsChecker

A check function (mapping deserialized claims to boolean) can be added to the authorizer.

A check failure results in a 403 (WWW-Authenticate: Bearer error="insufficient_scope") error.

Example:


    use jwt_authorizer::{JwtAuthorizer};
    use serde::Deserialize;

    // Authorized entity, struct deserializable from JWT claims
    #[derive(Debug, Deserialize, Clone)]
    struct User {
        sub: String,
    }

    let authorizer = JwtAuthorizer::from_rsa_pem("../config/jwtRS256.key.pub")
                    .check(
                        |claims: &User| claims.sub.contains('@') // must be an email
                    );

JWKS Refresh

By default the jwks keys are reloaded when a request token is signed with a key (kid jwt header) that is not present in the store (a minimal intervale between 2 reloads is 10s by default, can be configured).

Dependencies

~15–31MB
~494K SLoC