#key-set #jwt #jwks #axum #jwk #web-apps #authorization-header

axum-jwks

Use a JSON Web Key Set (JWKS) to verify JWTs in Axum

7 releases (breaking)

0.8.0 Aug 16, 2024
0.7.0 Mar 14, 2024
0.6.2 Jan 29, 2024
0.5.0 Aug 28, 2023
0.2.0 Mar 29, 2023

#592 in Authentication

Download history 27/week @ 2024-08-30 31/week @ 2024-09-06 7/week @ 2024-09-13 26/week @ 2024-09-20 71/week @ 2024-09-27 73/week @ 2024-10-04 105/week @ 2024-10-11 81/week @ 2024-10-18 7/week @ 2024-10-25 51/week @ 2024-11-01 52/week @ 2024-11-08 66/week @ 2024-11-15 43/week @ 2024-11-22 48/week @ 2024-11-29 104/week @ 2024-12-06 68/week @ 2024-12-13

278 downloads per month

MIT license

21KB
281 lines

axum-jwks

GitHub Workflow Status Crates.io docs.rs

Use a JSON Web Key Set (JWKS) to verify JWTs in Axum.

Features

  • Use an openid-configuration to get the setup from the Authorization Server.
  • Pull a JWKS directly from an Authorization Server
  • Verify JWTs signed by any key in the JWKS and provided as a bearer token in the Authorization header

For more information, see the crate documentation.


lib.rs:

axum-jwks allows for easily verifying JWTs in an axum application using any key from a JSON Web Key Set (JWKS).

Usage

Here's a minimal working example of how you would authenticate via JWTs in a route handler:

use axum::{
    async_trait,
    extract::{FromRef, FromRequestParts},
    http::request::Parts,
    http::status::StatusCode,
    response::{IntoResponse, Response},
    routing::get,
    Json,
    Router,
};
use axum_jwks::{Claims, Jwks, ParseTokenClaims, TokenError};
use serde::{Deserialize, Serialize};

// The state available to all your route handlers.
#[derive(Clone)]
struct AppState {
    jwks: Jwks,
}

impl FromRef<AppState> for Jwks {
    fn from_ref(state: &AppState) -> Self {
        state.jwks.clone()
    }
}

// The specific claims you want to parse from received JWTs.
#[derive(Deserialize, Serialize)]
struct TokenClaims {
    pub sub: String
}

impl ParseTokenClaims for TokenClaims {
    type Rejection = TokenClaimsError;
}

enum TokenClaimsError {
    Missing,
    Invalid,
}

impl IntoResponse for TokenClaimsError {
    fn into_response(self) -> Response {
        // You could do something more informative here like providing a
        // response body with different error messages for missing vs.
        // invalid tokens.
        StatusCode::UNAUTHORIZED.into_response()
    }
}

impl From<TokenError> for TokenClaimsError {
    fn from(value: TokenError) -> Self {
        match value {
            TokenError::Missing => Self::Missing,
            other => Self::Invalid,
        }
    }
}

// Handler that echos back the claims it receives. If the handler receives
// these claims, it's guaranteed that they come from a JWT that is signed
// by a key from the JWKS and is valid for the specified audience.
async fn echo_claims(Claims(claims): Claims<TokenClaims>) -> Json<TokenClaims> {
    Json(claims)
}

async fn create_router() -> Router<AppState> {
    let jwks = Jwks::from_oidc_url(
        // The Authorization Server that signs the JWTs you want to consume.
        "https://my-auth-server.example.com/.well-known/openid-configuration",
        // The audience identifier for the application. This ensures that
        // JWTs are intended for this application.
        Some("https://my-api-identifier.example.com/"),
    )
        .await
        .unwrap();

    Router::new()
        .route("/echo-claims", get(echo_claims))
        .with_state(AppState { jwks })
}

Dependencies

~14–27MB
~498K SLoC