jwt_extractor.rs
178 lignes · 5692 octets
//! JWT token extraction from HTTP requests //! //! This module provides functionality to extract JWT tokens from HTTP requests, //! supporting both cookie-based (web) and header-based (API) authentication. //! //! # Extraction Priority //! //! 1. **Authorization Header**: `Authorization: Bearer <token>` (for API clients) //! 2. **Cookie**: `jwt_token` cookie (for web browsers) //! //! This priority allows API clients to override cookie-based authentication //! by explicitly providing a token in the Authorization header. use axum::extract::Request; use axum_extra::extract::CookieJar; use crate::error::AppError; /// Extract JWT token from HTTP request /// /// Attempts to extract a JWT token from the request in the following order: /// 1. `Authorization: Bearer <token>` header (for API) /// 2. `jwt_token` cookie (for web) /// /// # Arguments /// /// * `request` - The HTTP request /// /// # Returns /// /// `Ok(String)` with the JWT token if found, `AppError` otherwise /// /// # Errors /// /// Returns `AppError::Unauthorized` if no token is found in either location /// /// # Examples /// /// ```rust,ignore /// use rustwarden_core::auth::jwt_extractor::extract_jwt_from_request; /// /// // In a middleware or handler /// let jwt_token = extract_jwt_from_request(&request)?; /// ``` pub fn extract_jwt_from_request(request: &Request) -> Result<String, AppError> { // Priority 1: Check Authorization header (for API) if let Some(auth_header) = request.headers().get("authorization") { if let Ok(auth_str) = auth_header.to_str() { if let Some(token) = extract_bearer_token(auth_str) { tracing::debug!("JWT extracted from Authorization header"); return Ok(token); } } } // Priority 2: Check cookie (for web) let cookie_jar = CookieJar::from_headers(request.headers()); if let Some(cookie) = cookie_jar.get("jwt_token") { let token = cookie.value().to_string(); if !token.is_empty() { tracing::debug!("JWT extracted from cookie"); return Ok(token); } } // No token found tracing::debug!("No JWT token found in request"); Err(AppError::InvalidToken) } /// Extract JWT token from request parts (headers only, no body needed) /// /// This function extracts a JWT token from request parts (headers/cookies). /// Useful when you only have access to request parts, not the full request. /// /// # Arguments /// /// * `parts` - Request parts (contains headers, URI, etc.) /// /// # Returns /// /// `Ok(String)` with the JWT token if found, `AppError` otherwise pub fn extract_jwt_from_request_parts( parts: &axum::http::request::Parts, ) -> Result<String, AppError> { // Priority 1: Check Authorization header (for API) if let Some(auth_header) = parts.headers.get("authorization") { if let Ok(auth_str) = auth_header.to_str() { if let Some(token) = extract_bearer_token(auth_str) { tracing::debug!("JWT extracted from Authorization header"); return Ok(token); } } } // Priority 2: Check cookie (for web) let cookie_jar = axum_extra::extract::CookieJar::from_headers(&parts.headers); if let Some(cookie) = cookie_jar.get("jwt_token") { let token = cookie.value().to_string(); if !token.is_empty() { tracing::debug!("JWT extracted from cookie"); return Ok(token); } } // No token found tracing::debug!("No JWT token found in request parts"); Err(AppError::InvalidToken) } /// Extract Bearer token from Authorization header value /// /// Parses strings like `"Bearer <token>"` and returns the token part. /// /// # Arguments /// /// * `auth_header` - The Authorization header value (e.g., "Bearer eyJhbGci...") /// /// # Returns /// /// `Some(String)` with the token if valid Bearer format, `None` otherwise /// /// # Examples /// /// ``` /// use rustwarden_core::auth::jwt_extractor::extract_bearer_token; /// /// let token = extract_bearer_token("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."); /// assert_eq!(token, Some("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...".to_string())); /// ``` pub fn extract_bearer_token(auth_header: &str) -> Option<String> { // Check if it starts with "Bearer " if auth_header.len() > 7 && auth_header[..7].eq_ignore_ascii_case("bearer ") { let token = auth_header[7..].trim(); if !token.is_empty() { return Some(token.to_string()); } } None } #[cfg(test)] mod tests { use super::*; #[test] fn test_extract_bearer_token() { // Valid Bearer token assert_eq!( extract_bearer_token("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"), Some("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9".to_string()) ); // Bearer with extra spaces assert_eq!( extract_bearer_token("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 "), Some("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9".to_string()) ); // Case insensitive assert_eq!( extract_bearer_token("bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"), Some("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9".to_string()) ); // Invalid format assert_eq!( extract_bearer_token("Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"), None ); assert_eq!( extract_bearer_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"), None ); assert_eq!(extract_bearer_token("Bearer"), None); assert_eq!(extract_bearer_token(""), None); } }
GitRust