micromegas_auth/
axum.rs

1//! Axum middleware for HTTP authentication
2//!
3//! Provides authentication middleware for Axum HTTP services that:
4//! 1. Extracts request parts (headers, method, URI)
5//! 2. Validates using configured AuthProvider
6//! 3. Injects AuthContext into request extensions
7//! 4. Returns 401 Unauthorized on auth failures
8
9use crate::types::{AuthProvider, HttpRequestParts, RequestParts};
10use axum::{
11    extract::Request,
12    http::StatusCode,
13    middleware::Next,
14    response::{IntoResponse, Response},
15};
16use micromegas_tracing::prelude::*;
17use std::sync::Arc;
18
19/// Axum middleware for request-based authentication
20///
21/// This middleware extracts request parts (headers, method, URI),
22/// validates them using the provided AuthProvider, and injects the resulting
23/// AuthContext into the request extensions.
24///
25/// # Example
26///
27/// ```rust,ignore
28/// use axum::{Router, middleware};
29/// use micromegas_auth::axum::auth_middleware;
30/// use micromegas_auth::api_key::ApiKeyAuthProvider;
31/// use std::sync::Arc;
32///
33/// let auth_provider = Arc::new(ApiKeyAuthProvider::new(keyring));
34/// let app = Router::new()
35///     .layer(middleware::from_fn(move |req, next| {
36///         auth_middleware(auth_provider.clone(), req, next)
37///     }));
38/// ```
39pub async fn auth_middleware(
40    auth_provider: Arc<dyn AuthProvider>,
41    mut req: Request,
42    next: Next,
43) -> Result<Response, AuthError> {
44    // Extract request parts for authentication
45    let parts = HttpRequestParts {
46        headers: req.headers().clone(),
47        method: req.method().clone(),
48        uri: req.uri().clone(),
49    };
50
51    // Validate request using auth provider
52    let auth_ctx = auth_provider
53        .validate_request(&parts as &dyn RequestParts)
54        .await
55        .map_err(|e| {
56            warn!("[auth_failure] {e}");
57            AuthError::InvalidToken
58        })?;
59
60    // Log successful authentication (trace level to avoid noise on every request)
61    trace!(
62        "[auth_success] subject={} email={:?} issuer={} admin={}",
63        auth_ctx.subject, auth_ctx.email, auth_ctx.issuer, auth_ctx.is_admin
64    );
65
66    // SECURITY: Remove any client-provided auth headers to prevent spoofing
67    // These headers should only be trusted when set by the authentication layer
68    // The AuthContext in request extensions is the authoritative source
69    req.headers_mut().remove("x-auth-subject");
70    req.headers_mut().remove("x-auth-email");
71    req.headers_mut().remove("x-auth-issuer");
72    req.headers_mut().remove("x-allow-delegation");
73
74    // Inject auth context into request extensions for downstream handlers
75    req.extensions_mut().insert(auth_ctx);
76
77    // Continue to next middleware/handler
78    Ok(next.run(req).await)
79}
80
81/// Authentication errors for HTTP responses
82#[derive(Debug)]
83pub enum AuthError {
84    /// Token validation failed
85    InvalidToken,
86}
87
88impl IntoResponse for AuthError {
89    fn into_response(self) -> Response {
90        let (status, message) = match self {
91            AuthError::InvalidToken => (StatusCode::UNAUTHORIZED, "Invalid token"),
92        };
93
94        (status, message).into_response()
95    }
96}