You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2 週之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. //! Bearer-token authentication extractor.
  2. //!
  3. //! Use `AuthenticatedUser` as a handler parameter to require a valid JWT.
  4. //! Unauthenticated requests are rejected with `401 Unauthorized` before the
  5. //! handler body ever executes.
  6. //!
  7. //! # Example
  8. //!
  9. //! ```rust,ignore
  10. //! async fn protected_handler(
  11. //! user: AuthenticatedUser,
  12. //! State(state): State<AppState>,
  13. //! ) -> Result<impl IntoResponse, ApiError> {
  14. //! // user.id and user.roles are guaranteed valid here
  15. //! Ok(Json(json!({ "sub": user.id })))
  16. //! }
  17. //! ```
  18. use axum::{
  19. async_trait,
  20. extract::FromRequestParts,
  21. http::request::Parts,
  22. RequestPartsExt,
  23. };
  24. use axum_extra::{
  25. headers::{authorization::Bearer, Authorization},
  26. TypedHeader,
  27. };
  28. use uuid::Uuid;
  29. use crate::errors::ApiError;
  30. // ── Authenticated user claim ──────────────────────────────────────────────────
  31. /// Injected into handlers that require authentication.
  32. #[derive(Debug, Clone)]
  33. pub struct AuthenticatedUser {
  34. pub id: Uuid,
  35. pub roles: Vec<String>,
  36. }
  37. // ── FromRequestParts impl ─────────────────────────────────────────────────────
  38. #[async_trait]
  39. impl<S> FromRequestParts<S> for AuthenticatedUser
  40. where
  41. S: Send + Sync,
  42. {
  43. type Rejection = ApiError;
  44. async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
  45. // Extract the Bearer token from the Authorization header.
  46. let TypedHeader(Authorization(bearer)) = parts
  47. .extract::<TypedHeader<Authorization<Bearer>>>()
  48. .await
  49. .map_err(|_| ApiError::Unauthorized("Missing or malformed Authorization header".to_string()))?;
  50. // TODO: replace this stub with real JWT validation.
  51. //
  52. // Example using `jsonwebtoken`:
  53. // let secret = state.config().auth.jwt_secret.as_bytes();
  54. // let token_data = decode::<Claims>(bearer.token(), &DecodingKey::from_secret(secret), &Validation::default())?;
  55. // return Ok(AuthenticatedUser { id: token_data.claims.sub, roles: token_data.claims.roles });
  56. let _ = bearer.token(); // silence unused-variable lint on the stub
  57. Err(ApiError::Unauthorized("JWT validation not yet implemented".to_string()))
  58. }
  59. }