//! Bearer-token authentication extractor. //! //! Use `AuthenticatedUser` as a handler parameter to require a valid JWT. //! Unauthenticated requests are rejected with `401 Unauthorized` before the //! handler body ever executes. //! //! # Example //! //! ```rust,ignore //! async fn protected_handler( //! user: AuthenticatedUser, //! State(state): State, //! ) -> Result { //! // user.id and user.roles are guaranteed valid here //! Ok(Json(json!({ "sub": user.id }))) //! } //! ``` use axum::{ async_trait, extract::FromRequestParts, http::request::Parts, RequestPartsExt, }; use axum_extra::{ headers::{authorization::Bearer, Authorization}, TypedHeader, }; use uuid::Uuid; use crate::errors::ApiError; // ── Authenticated user claim ────────────────────────────────────────────────── /// Injected into handlers that require authentication. #[derive(Debug, Clone)] pub struct AuthenticatedUser { pub id: Uuid, pub roles: Vec, } // ── FromRequestParts impl ───────────────────────────────────────────────────── #[async_trait] impl FromRequestParts for AuthenticatedUser where S: Send + Sync, { type Rejection = ApiError; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { // Extract the Bearer token from the Authorization header. let TypedHeader(Authorization(bearer)) = parts .extract::>>() .await .map_err(|_| ApiError::Unauthorized("Missing or malformed Authorization header".to_string()))?; // TODO: replace this stub with real JWT validation. // // Example using `jsonwebtoken`: // let secret = state.config().auth.jwt_secret.as_bytes(); // let token_data = decode::(bearer.token(), &DecodingKey::from_secret(secret), &Validation::default())?; // return Ok(AuthenticatedUser { id: token_data.claims.sub, roles: token_data.claims.roles }); let _ = bearer.token(); // silence unused-variable lint on the stub Err(ApiError::Unauthorized("JWT validation not yet implemented".to_string())) } }