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

axum-jwks

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

6 releases (breaking)

0.7.0 Mar 14, 2024
0.6.2 Jan 29, 2024
0.5.0 Aug 28, 2023
0.4.0 Apr 5, 2023
0.2.0 Mar 29, 2023

#354 in Authentication

Download history 12/week @ 2024-01-26 2/week @ 2024-02-02 28/week @ 2024-02-16 112/week @ 2024-02-23 54/week @ 2024-03-01 46/week @ 2024-03-08 169/week @ 2024-03-15 46/week @ 2024-03-22 41/week @ 2024-03-29 51/week @ 2024-04-05 15/week @ 2024-04-12

156 downloads per month

MIT license

21KB
279 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.
        "https://my-api-identifier.example.com/".to_owned(),
    )
        .await
        .unwrap();

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

Dependencies

~14–29MB
~531K SLoC