axum_conf/fluent/auth.rs
1//! Authentication middleware: OIDC, Basic Auth, and user span recording.
2
3use super::router::FluentRouter;
4use super::user_span;
5
6#[allow(unused_imports)]
7use crate::{HttpMiddleware, Result};
8
9#[cfg(feature = "keycloak")]
10use {
11 crate::Role,
12 axum_keycloak_auth::{
13 PassthroughMode, Url, decode::ProfileAndEmail, instance::KeycloakAuthInstance,
14 instance::KeycloakConfig, layer::KeycloakAuthLayer,
15 },
16};
17
18#[cfg(feature = "basic-auth")]
19use {super::basic_auth, std::sync::Arc};
20
21impl<State> FluentRouter<State>
22where
23 State: Clone + Send + Sync + 'static,
24{
25 /// Sets up OpenID Connect (OIDC) authentication using Keycloak.
26 ///
27 /// Configures JWT token validation and role-based access control. Requires
28 /// the `keycloak` feature to be enabled.
29 ///
30 /// # Configuration
31 ///
32 /// ```toml
33 /// [http.oidc]
34 /// issuer_url = "https://keycloak.example.com/realms/myrealm"
35 /// realm = "myrealm"
36 /// audiences = ["my-client"]
37 /// client_id = "my-client"
38 /// client_secret = "{{ KEYCLOAK_CLIENT_SECRET }}"
39 /// ```
40 ///
41 /// # Returns
42 ///
43 /// A `Result` containing the configured router or an error if OIDC setup fails.
44 ///
45 /// # Errors
46 ///
47 /// Returns an error if:
48 /// - OIDC configuration is invalid
49 /// - Cannot connect to the Keycloak server
50 /// - Client credentials are incorrect
51 #[cfg(feature = "keycloak")]
52 pub fn setup_oidc(mut self) -> Result<Self> {
53 if let Some(oidc) = &self.config.http.oidc
54 && self.is_middleware_enabled(HttpMiddleware::Oidc)
55 {
56 tracing::trace!(
57 realm = %oidc.realm,
58 issuer_url = %oidc.issuer_url,
59 "OIDC middleware enabled"
60 );
61 let keycloak_auth_instance = KeycloakAuthInstance::new(
62 KeycloakConfig::builder()
63 .server(Url::parse(&oidc.issuer_url)?)
64 .realm(oidc.realm.clone())
65 .build(),
66 );
67
68 self.inner = self.inner.route_layer(
69 KeycloakAuthLayer::<Role, ProfileAndEmail>::builder()
70 .instance(keycloak_auth_instance)
71 .passthrough_mode(PassthroughMode::Block)
72 .expected_audiences(oidc.audiences.clone())
73 .persist_raw_claims(true)
74 .build(),
75 );
76 }
77 Ok(self)
78 }
79
80 /// Sets up HTTP Basic Auth and/or API Key authentication.
81 ///
82 /// When configured, protects all routes (except health endpoints) with authentication.
83 /// Supports HTTP Basic Auth (RFC 7617), API Key authentication, or both.
84 ///
85 /// # Configuration
86 ///
87 /// ```toml
88 /// [http.basic_auth]
89 /// mode = "either" # "basic", "api_key", or "either"
90 /// api_key_header = "X-API-Key"
91 ///
92 /// [[http.basic_auth.users]]
93 /// username = "admin"
94 /// password = "{{ ADMIN_PASSWORD }}"
95 ///
96 /// [[http.basic_auth.api_keys]]
97 /// key = "{{ API_KEY }}"
98 /// name = "service-a"
99 /// ```
100 ///
101 /// # Extracting Identity in Handlers
102 ///
103 /// ```rust,ignore
104 /// use axum::Extension;
105 /// use axum_conf::AuthenticatedIdentity;
106 ///
107 /// async fn handler(Extension(identity): Extension<AuthenticatedIdentity>) -> String {
108 /// format!("Hello, {}!", identity.name)
109 /// }
110 /// ```
111 #[cfg(feature = "basic-auth")]
112 pub fn setup_basic_auth(mut self) -> Result<Self> {
113 if let Some(basic_auth_config) = &self.config.http.basic_auth
114 && self.is_middleware_enabled(HttpMiddleware::BasicAuth)
115 {
116 tracing::trace!(
117 mode = ?basic_auth_config.mode,
118 "BasicAuth middleware enabled"
119 );
120 let config = Arc::new(basic_auth_config.clone());
121
122 self.inner = self
123 .inner
124 .route_layer(axum::middleware::from_fn(move |request, next| {
125 let config = Arc::clone(&config);
126 basic_auth::basic_auth_middleware(config, request, next)
127 }));
128 }
129 Ok(self)
130 }
131
132 /// Records the authenticated username to the logging span.
133 ///
134 /// This middleware runs after authentication and records the username
135 /// to the `user` field of the current tracing span. This ensures all
136 /// subsequent logs within the request include the authenticated user.
137 ///
138 /// Works with both Basic Auth (`AuthenticatedIdentity`) and OIDC
139 /// (`KeycloakToken`) authentication methods.
140 ///
141 /// For unauthenticated requests (e.g., health endpoints), the `user`
142 /// field remains empty and won't appear in log output.
143 #[must_use]
144 pub fn setup_user_span(mut self) -> Self {
145 tracing::trace!("UserSpan middleware enabled");
146 self.inner = self
147 .inner
148 .layer(axum::middleware::from_fn(user_span::record_user_to_span));
149 self
150 }
151}