Overview
The protocol family lands in Rust as three composable crates rather than a framework module: oauth2 (the flows, as a typestate-guided client), openidconnect (OIDC discovery, ID-token validation and claims on top of oauth2), and jsonwebtoken (JWT signing/verification, the resource-server workhorse). The architecture matches Go's — client crates plus hand-wired middleware, external IdP assumed — with Rust's characteristic twist: misuse the API and it usually fails to compile (PKCE verifiers and CSRF tokens are distinct types you must thread through, not strings you may forget).
Key points
- oauth2 crate:
BasicClientconfigured with endpoints; auth-code + PKCE flow as typed steps —authorize_url()hands back aCsrfTokenand takes aPkceCodeChallenge;exchange_code()demands the matching verifier. Client credentials, device code and refresh flows included; async via a pluggable reqwest hook. - openidconnect crate:
CoreClient::from_provider_metadata(.well-knowndiscovery), nonce-checked ID-token validation with signature, issuer, audience and expiry verification typed into the API; the claims struct is generic for custom claims. This is the browser-login half. - Resource server = jsonwebtoken + JWKS: fetch the IdP's JWKS (a small
reqwest+cache affair — or jwks-client-style crates), pick the key by
kid, thenjsonwebtoken::decode::<Claims>with an explicitValidation(algorithms, audience, issuer). Wire it as an axum extractor soClaimsin a handler signature is the auth requirement. - Framework glue: axum-login (on tower-sessions) provides the session-based login/backend abstraction for first-party auth; actix-identity/actix-session mirror it; middleware-level token validation is a thin tower layer either way.
- Serving OAuth2 yourself is the thin corner: oxide-auth exists (framework-agnostic authorization-server toolkit), but there is no Keycloak-class Rust IdP — the assumed architecture is an external IdP (Keycloak, Auth0, Zitadel, Ory), same conclusion as Go and Quarkus.
- The usual discipline is unchanged: authorization code + PKCE for anything
user-facing, validate
aud/iss/exp/nbfexplicitly, pin allowed algorithms (never acceptnone— jsonwebtoken won't), short-lived access tokens; protocol semantics in Authentication & authorization.
Examples
#[derive(Debug, Deserialize)]
struct Claims { sub: String, scope: String, exp: usize }
impl<S: Send + Sync> FromRequestParts<S> for Claims {
type Rejection = AppError;
async fn from_request_parts(parts: &mut Parts, _s: &S) -> Result<Self, Self::Rejection> {
let token = bearer_token(parts).ok_or(AppError::Unauthorized)?;
let key = JWKS.decoding_key_for(&token)?; // kid-matched, cached
let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&["orders-api"]);
validation.set_issuer(&["https://idp.example.com/"]);
Ok(decode::<Claims>(token, &key, &validation)?.claims)
}
}
async fn list_orders(claims: Claims) -> Result<Json<Vec<Order>>, AppError> { /* … */ }
Related
- Security in Rust applications — the map — parent map.
- Web security in Rust apps — sessions, cookies and CSRF around the login flows.
- Cryptography in Rust — the signature primitives beneath JWT validation.
- Authentication & authorization — the protocol theory.
- OAuth2 & OIDC in Java and in Go — the framework-integrated and library-composed counterparts.