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}