Skip to main content

actix_jwt/
middleware.rs

1//! Central JWT authentication middleware for actix-web.
2//!
3//! This module is a Rust port of
4//! [`EchoJWTMiddleware`](https://github.com/LdDl/echo-jwt/blob/master/auth_jwt.go)
5//! from the Go implementation.
6
7use std::collections::HashMap;
8use std::future::{Future, Ready, ready};
9use std::pin::Pin;
10use std::sync::Arc;
11use std::time::Duration;
12
13use actix_web::cookie::{Cookie, SameSite};
14use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
15use actix_web::http::header;
16use actix_web::{HttpMessage, HttpRequest, HttpResponse, HttpResponseBuilder};
17use base64::Engine;
18use base64::engine::general_purpose::URL_SAFE;
19use chrono::{DateTime, Utc};
20use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
21use serde_json::Value;
22use tracing::warn;
23
24use crate::core::{Token, TokenStore};
25use crate::errors::JwtError;
26use crate::store::InMemoryRefreshTokenStore;
27
28/// JWT claims map stored in [`HttpRequest`] extensions by the middleware.
29///
30/// Retrieve it in a handler via [`extract_claims`].
31#[derive(Debug, Clone)]
32pub struct JwtPayload(pub HashMap<String, Value>);
33
34/// Raw JWT token string stored in [`HttpRequest`] extensions.
35///
36/// Retrieve it in a handler via [`get_token`].
37#[derive(Debug, Clone)]
38pub struct JwtTokenString(pub String);
39
40/// Identity value stored in [`HttpRequest`] extensions.
41///
42/// Retrieve it in a handler via [`get_identity`].
43#[derive(Debug, Clone)]
44pub struct JwtIdentity(pub Value);
45
46/// Central JWT authentication middleware for actix-web.
47///
48/// A full-featured Rust port of
49/// [`EchoJWTMiddleware`](https://github.com/LdDl/echo-jwt/blob/master/auth_jwt.go)
50/// from the Go implementation,
51/// providing:
52///
53/// * Login / logout / refresh handlers with token rotation.
54/// * Access + refresh token generation ([RFC 6749]).
55/// * Token extraction from header, query, cookie, path param, form.
56/// * Cookie management (access + refresh).
57/// * Skipper, BeforeFunc, SuccessHandler, ErrorHandler (labstack features).
58/// * HMAC and RSA signing (with optional passphrase-protected private keys).
59///
60/// # Lifecycle
61///
62/// 1. Create via [`ActixJwtMiddleware::new`].
63/// 2. Configure fields (key, callbacks, etc.).
64/// 3. Call [`init`](Self::init) to validate and prepare keys.
65/// 4. Wrap in `Arc`, share via `web::Data` and register routes / middleware.
66///
67/// [RFC 6749]: https://datatracker.ietf.org/doc/html/rfc6749
68///
69/// # Examples
70///
71/// ```rust,no_run
72/// use std::sync::Arc;
73/// use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
74/// use actix_jwt::{ActixJwtMiddleware, extract_claims, JwtError};
75///
76/// #[actix_web::main]
77/// async fn main() -> std::io::Result<()> {
78///     let mut jwt = ActixJwtMiddleware::new();
79///     jwt.key = b"secret".to_vec();
80///     jwt.authenticator = Some(Arc::new(|_req, body| {
81///         #[derive(serde::Deserialize)]
82///         struct L { username: String, password: String }
83///         let result = serde_json::from_slice::<L>(body)
84///             .map(|c| serde_json::json!({"user": c.username}))
85///             .map_err(|_| JwtError::MissingLoginValues);
86///         Box::pin(async move { result })
87///     }));
88///     jwt.init().unwrap();
89///
90///     let jwt = Arc::new(jwt);
91///     let jwt_data = web::Data::new(jwt.clone());
92///
93///     HttpServer::new(move || {
94///         App::new()
95///             .app_data(jwt_data.clone())
96///             .route("/login", web::post().to({
97///                 let j = jwt.clone();
98///                 move |req: HttpRequest, body: web::Bytes| {
99///                     let j = j.clone();
100///                     async move { j.login_handler(&req, &body).await }
101///                 }
102///             }))
103///             .service(
104///                 web::scope("/api")
105///                     .wrap(jwt.middleware())
106///                     .route("/me", web::get().to(|req: HttpRequest| async move {
107///                         HttpResponse::Ok().json(extract_claims(&req))
108///                     })),
109///             )
110///     })
111///     .bind("127.0.0.1:8080")?
112///     .run()
113///     .await
114/// }
115/// ```
116pub struct ActixJwtMiddleware {
117    /// `WWW-Authenticate` realm value.
118    pub realm: String,
119    /// Key used to store the identity value in request extensions.
120    pub identity_key: String,
121
122    /// Signing algorithm name (`"HS256"`, `"RS256"`, etc.).
123    pub signing_algorithm: String,
124    /// HMAC secret key bytes (used when `signing_algorithm` is `HS*`).
125    pub key: Vec<u8>,
126    /// Optional callback for multi-key (KID) support. When set, `key` and
127    /// RSA key fields are ignored for **decoding**.
128    pub key_func:
129        Option<Arc<dyn Fn(&jsonwebtoken::Header) -> Result<DecodingKey, JwtError> + Send + Sync>>,
130
131    /// Access token lifetime (default: 1 hour).
132    pub timeout: Duration,
133    /// Optional per-user timeout override based on the identity payload.
134    pub timeout_func: Option<Arc<dyn Fn(&Value) -> Duration + Send + Sync>>,
135    /// Maximum duration for which a token can be refreshed (0 = disabled).
136    pub max_refresh: Duration,
137    /// Clock function (override for testing).
138    pub time_func: Arc<dyn Fn() -> DateTime<Utc> + Send + Sync>,
139
140    /// Validates login credentials and returns user data on success.
141    ///
142    /// The callback receives the request and raw body bytes, and returns a
143    /// future that resolves to the authenticated user data (or an error).
144    /// Extract what you need from the arguments synchronously, then return
145    /// an async block that owns all captured data.
146    pub authenticator: Option<
147        Arc<
148            dyn Fn(
149                    &HttpRequest,
150                    &[u8],
151                )
152                    -> Pin<Box<dyn Future<Output = Result<Value, JwtError>> + Send>>
153                + Send
154                + Sync,
155        >,
156    >,
157    /// Decides whether the authenticated identity is allowed to proceed.
158    pub authorizer: Arc<dyn Fn(&HttpRequest, &Value) -> bool + Send + Sync>,
159    /// Maps user data to custom JWT claims.
160    pub payload_func: Option<Arc<dyn Fn(&Value) -> HashMap<String, Value> + Send + Sync>>,
161    /// Extracts the identity value from request extensions.
162    pub identity_handler: Arc<dyn Fn(&HttpRequest) -> Option<Value> + Send + Sync>,
163
164    /// Builds the "unauthorized" HTTP response.
165    pub unauthorized: Arc<dyn Fn(&HttpRequest, u16, &str) -> HttpResponse + Send + Sync>,
166    /// Builds the login success response.
167    pub login_response: Arc<dyn Fn(&HttpRequest, &Token) -> HttpResponse + Send + Sync>,
168    /// Builds the logout success response.
169    pub logout_response: Arc<dyn Fn(&HttpRequest) -> HttpResponse + Send + Sync>,
170    /// Builds the refresh success response.
171    pub refresh_response: Arc<dyn Fn(&HttpRequest, &Token) -> HttpResponse + Send + Sync>,
172    /// Maps a [`JwtError`] to a human-readable message for the response body.
173    pub http_status_message_func: Arc<dyn Fn(&HttpRequest, &JwtError) -> String + Send + Sync>,
174
175    /// Comma-separated list of `"source:name"` pairs (e.g.
176    /// `"header:Authorization,query:token"`).
177    pub token_lookup: String,
178    /// Expected prefix in the `Authorization` header (default: `"Bearer"`).
179    pub token_head_name: String,
180    /// Name of the expiration claim (default: `"exp"`).
181    pub exp_field: String,
182
183    /// Path to an RSA private key PEM file.
184    pub priv_key_file: Option<String>,
185    /// RSA private key PEM bytes (alternative to file).
186    pub priv_key_bytes: Option<Vec<u8>>,
187    /// Path to an RSA public key PEM file.
188    pub pub_key_file: Option<String>,
189    /// RSA public key PEM bytes (alternative to file).
190    pub pub_key_bytes: Option<Vec<u8>>,
191    /// Passphrase for encrypted PKCS#8 private keys.
192    pub private_key_passphrase: Option<String>,
193    encoding_key: Option<EncodingKey>,
194    decoding_key: Option<DecodingKey>,
195
196    /// When `true`, access and refresh tokens are also sent as cookies.
197    pub send_cookie: bool,
198    /// Max-Age for the access-token cookie.
199    pub cookie_max_age: Duration,
200    /// Sets the `Secure` flag on cookies.
201    pub secure_cookie: bool,
202    /// Sets the `HttpOnly` flag on cookies.
203    pub cookie_http_only: bool,
204    /// Optional domain for cookies.
205    pub cookie_domain: Option<String>,
206    /// Name of the access-token cookie (default: `"jwt"`).
207    pub cookie_name: String,
208    /// `SameSite` attribute for cookies (default: `Lax`).
209    pub cookie_same_site: SameSite,
210    /// When `true`, the validated token is echoed back in the response
211    /// `Authorization` header.
212    pub send_authorization: bool,
213
214    /// Refresh-token lifetime (default: 30 days).
215    pub refresh_token_timeout: Duration,
216    /// Cookie name for the refresh token (default: `"refresh_token"`).
217    pub refresh_token_cookie_name: String,
218    /// Length of the random refresh-token bytes before base64 encoding.
219    pub refresh_token_length: usize,
220    /// Pluggable storage backend for refresh tokens.
221    pub refresh_token_store: Arc<dyn TokenStore>,
222
223    /// When this returns `true` for a request the middleware is bypassed
224    /// entirely (labstack feature).
225    pub skipper: Option<Arc<dyn Fn(&ServiceRequest) -> bool + Send + Sync>>,
226    /// Called before token extraction (labstack feature).
227    pub before_func: Option<Arc<dyn Fn(&ServiceRequest) + Send + Sync>>,
228    /// Called after successful token validation (labstack feature).
229    pub success_handler: Option<Arc<dyn Fn(&HttpRequest) -> Result<(), JwtError> + Send + Sync>>,
230    /// Intercepts errors; returning `None` suppresses the error (labstack
231    /// feature).
232    pub error_handler:
233        Option<Arc<dyn Fn(&HttpRequest, JwtError) -> Option<JwtError> + Send + Sync>>,
234    /// When `true` **and** `error_handler` returns `None`, the request is
235    /// forwarded to the inner service instead of being rejected (hybrid /
236    /// public+auth routes).
237    pub continue_on_ignored_error: bool,
238}
239
240impl ActixJwtMiddleware {
241    /// Creates a new middleware instance with sensible defaults.
242    ///
243    /// Mirrors
244    /// [`MiddlewareInit`](https://github.com/LdDl/echo-jwt/blob/master/auth_jwt.go)
245    /// defaults from the Go implementation.  You **must** call [`init`](Self::init) after configuring
246    /// the instance.
247    pub fn new() -> Self {
248        Self {
249            realm: "actix jwt".to_string(),
250            identity_key: "identity".to_string(),
251
252            signing_algorithm: "HS256".to_string(),
253            key: Vec::new(),
254            key_func: None,
255
256            timeout: Duration::from_secs(3600), // 1 hour
257            timeout_func: None,
258            max_refresh: Duration::ZERO,
259            time_func: Arc::new(Utc::now),
260
261            authenticator: None,
262            authorizer: Arc::new(|_req, _data| true),
263            payload_func: None,
264            identity_handler: Arc::new(|req| {
265                let ext = req.extensions();
266                let payload = ext.get::<JwtPayload>()?;
267                payload.0.get("identity").cloned()
268            }),
269
270            unauthorized: Arc::new(|_req, code, message| {
271                HttpResponse::build(
272                    actix_web::http::StatusCode::from_u16(code)
273                        .unwrap_or(actix_web::http::StatusCode::UNAUTHORIZED),
274                )
275                .json(serde_json::json!({
276                    "code": code,
277                    "message": message,
278                }))
279            }),
280            login_response: Arc::new(|_req, token| {
281                HttpResponse::Ok().json(Self::generate_token_response_static(token))
282            }),
283            logout_response: Arc::new(|_req| {
284                HttpResponse::Ok().json(serde_json::json!({ "code": 200 }))
285            }),
286            refresh_response: Arc::new(|_req, token| {
287                HttpResponse::Ok().json(Self::generate_token_response_static(token))
288            }),
289            http_status_message_func: Arc::new(|_req, err| err.to_string()),
290
291            token_lookup: "header:Authorization".to_string(),
292            token_head_name: "Bearer".to_string(),
293            exp_field: "exp".to_string(),
294
295            priv_key_file: None,
296            priv_key_bytes: None,
297            pub_key_file: None,
298            pub_key_bytes: None,
299            private_key_passphrase: None,
300            encoding_key: None,
301            decoding_key: None,
302
303            send_cookie: false,
304            cookie_max_age: Duration::from_secs(3600),
305            secure_cookie: false,
306            cookie_http_only: false,
307            cookie_domain: None,
308            cookie_name: "jwt".to_string(),
309            cookie_same_site: SameSite::Lax,
310            send_authorization: false,
311
312            refresh_token_timeout: Duration::from_secs(30 * 24 * 3600), // 30 days
313            refresh_token_cookie_name: "refresh_token".to_string(),
314            refresh_token_length: 32,
315            refresh_token_store: Arc::new(InMemoryRefreshTokenStore::new()),
316
317            skipper: None,
318            before_func: None,
319            success_handler: None,
320            error_handler: None,
321            continue_on_ignored_error: false,
322        }
323    }
324
325    /// Validates configuration and prepares signing / decoding keys.
326    ///
327    /// **Must be called before the middleware is used.** Mirrors
328    /// [`MiddlewareInit`](https://github.com/LdDl/echo-jwt/blob/master/auth_jwt.go)
329    /// from the Go implementation.
330    ///
331    /// # Errors
332    ///
333    /// Returns [`JwtError::MissingSecretKey`] when no HMAC key is set (and no
334    /// `key_func` or RSA key is configured).
335    pub fn init(&mut self) -> Result<(), JwtError> {
336        if self.token_lookup.is_empty() {
337            self.token_lookup = "header:Authorization".to_string();
338        }
339
340        if self.signing_algorithm.is_empty() {
341            self.signing_algorithm = "HS256".to_string();
342        }
343
344        if self.timeout == Duration::ZERO {
345            self.timeout = Duration::from_secs(3600);
346        }
347
348        let token_head = self.token_head_name.trim().to_string();
349        self.token_head_name = if token_head.is_empty() {
350            "Bearer".to_string()
351        } else {
352            token_head
353        };
354
355        if self.realm.is_empty() {
356            self.realm = "actix jwt".to_string();
357        }
358
359        if self.cookie_max_age == Duration::ZERO {
360            self.cookie_max_age = self.timeout;
361        }
362
363        if self.cookie_name.is_empty() {
364            self.cookie_name = "jwt".to_string();
365        }
366
367        if self.refresh_token_cookie_name.is_empty() {
368            self.refresh_token_cookie_name = "refresh_token".to_string();
369        }
370
371        if self.exp_field.is_empty() {
372            self.exp_field = "exp".to_string();
373        }
374
375        if self.identity_key.is_empty() {
376            self.identity_key = "identity".to_string();
377        }
378
379        if self.refresh_token_timeout == Duration::ZERO {
380            self.refresh_token_timeout = Duration::from_secs(30 * 24 * 3600);
381        }
382
383        if self.refresh_token_length == 0 {
384            self.refresh_token_length = 32;
385        }
386
387        // Bypass other key settings if KeyFunc is set
388        if self.key_func.is_some() {
389            return Ok(());
390        }
391
392        if self.using_public_key_algo() {
393            return self.read_keys();
394        }
395
396        if self.key.is_empty() {
397            return Err(JwtError::MissingSecretKey);
398        }
399
400        self.encoding_key = Some(EncodingKey::from_secret(&self.key));
401        self.decoding_key = Some(DecodingKey::from_secret(&self.key));
402
403        Ok(())
404    }
405
406    /// Returns `true` when the signing algorithm is RSA-based.
407    pub fn using_public_key_algo(&self) -> bool {
408        matches!(self.signing_algorithm.as_str(), "RS256" | "RS384" | "RS512")
409    }
410
411    /// Parse the `signing_algorithm` string into a `jsonwebtoken::Algorithm`.
412    fn algorithm(&self) -> Result<Algorithm, JwtError> {
413        match self.signing_algorithm.as_str() {
414            "HS256" => Ok(Algorithm::HS256),
415            "HS384" => Ok(Algorithm::HS384),
416            "HS512" => Ok(Algorithm::HS512),
417            "RS256" => Ok(Algorithm::RS256),
418            "RS384" => Ok(Algorithm::RS384),
419            "RS512" => Ok(Algorithm::RS512),
420            _ => Err(JwtError::InvalidSigningAlgorithm),
421        }
422    }
423
424    fn read_keys(&mut self) -> Result<(), JwtError> {
425        self.load_private_key()?;
426        self.load_public_key()?;
427        Ok(())
428    }
429
430    fn load_private_key(&mut self) -> Result<(), JwtError> {
431        let key_data = if let Some(ref path) = self.priv_key_file {
432            std::fs::read(path).map_err(|e| {
433                warn!("Failed to read private key file {}: {}", path, e);
434                JwtError::NoPrivKeyFile
435            })?
436        } else if let Some(ref bytes) = self.priv_key_bytes {
437            bytes.clone()
438        } else {
439            return Err(JwtError::NoPrivKeyFile);
440        };
441
442        if let Some(ref passphrase) = self.private_key_passphrase {
443            // Encrypted PKCS#8 private key
444            let pem_str = std::str::from_utf8(&key_data).map_err(|_| JwtError::InvalidPrivKey)?;
445            let doc = pkcs8::EncryptedPrivateKeyInfo::try_from(pem_str.as_bytes())
446                .map_err(|_| JwtError::InvalidPrivKey)?;
447
448            let decrypted = doc
449                .decrypt(passphrase.as_bytes())
450                .map_err(|_| JwtError::InvalidPrivKey)?;
451
452            let der_bytes = decrypted.as_bytes();
453
454            // Re-encode as PEM for jsonwebtoken
455            let pem = pem::encode(&pem::Pem::new("PRIVATE KEY", der_bytes.to_vec()));
456            self.encoding_key = Some(
457                EncodingKey::from_rsa_pem(pem.as_bytes()).map_err(|_| JwtError::InvalidPrivKey)?,
458            );
459        } else {
460            self.encoding_key =
461                Some(EncodingKey::from_rsa_pem(&key_data).map_err(|_| JwtError::InvalidPrivKey)?);
462        }
463
464        Ok(())
465    }
466
467    fn load_public_key(&mut self) -> Result<(), JwtError> {
468        let key_data = if let Some(ref path) = self.pub_key_file {
469            std::fs::read(path).map_err(|e| {
470                warn!("Failed to read public key file {}: {}", path, e);
471                JwtError::NoPubKeyFile
472            })?
473        } else if let Some(ref bytes) = self.pub_key_bytes {
474            bytes.clone()
475        } else {
476            return Err(JwtError::NoPubKeyFile);
477        };
478
479        self.decoding_key =
480            Some(DecodingKey::from_rsa_pem(&key_data).map_err(|_| JwtError::InvalidPubKey)?);
481
482        Ok(())
483    }
484
485    /// Generate a signed JWT access token. Returns `(token_string, expiry)`.
486    pub fn generate_access_token(&self, data: &Value) -> Result<(String, DateTime<Utc>), JwtError> {
487        let alg = self.algorithm()?;
488
489        let mut claims = serde_json::Map::new();
490
491        // Framework-controlled claims that PayloadFunc must not overwrite.
492        let framework_claims: &[&str] = &["exp", "orig_iat"];
493
494        if let Some(ref pf) = self.payload_func {
495            for (k, v) in pf(data) {
496                if !framework_claims.contains(&k.as_str()) {
497                    claims.insert(k, v);
498                }
499            }
500        }
501
502        let now = (self.time_func)();
503        let timeout = self
504            .timeout_func
505            .as_ref()
506            .map(|f| f(data))
507            .unwrap_or(self.timeout);
508        let expire = now
509            + chrono::Duration::from_std(timeout)
510                .unwrap_or_else(|_| chrono::Duration::seconds(3600));
511
512        claims.insert(
513            self.exp_field.clone(),
514            Value::Number(expire.timestamp().into()),
515        );
516        claims.insert(
517            "orig_iat".to_string(),
518            Value::Number(now.timestamp().into()),
519        );
520
521        let header = Header::new(alg);
522        let claims_value = Value::Object(claims);
523
524        let encoding_key = self
525            .encoding_key
526            .as_ref()
527            .ok_or(JwtError::MissingSecretKey)?;
528
529        let token_string = jsonwebtoken::encode(&header, &claims_value, encoding_key)
530            .map_err(|_| JwtError::FailedTokenCreation)?;
531
532        Ok((token_string, expire))
533    }
534
535    /// Generate a cryptographically secure random refresh token (base64url-encoded).
536    pub fn generate_refresh_token(&self) -> Result<String, JwtError> {
537        use rand::RngCore;
538        let mut buf = vec![0u8; self.refresh_token_length];
539        rand::thread_rng()
540            .try_fill_bytes(&mut buf)
541            .map_err(|e| JwtError::Internal(format!("RNG failure: {e}")))?;
542        Ok(URL_SAFE.encode(&buf))
543    }
544
545    /// Store a refresh token with associated user data.
546    async fn store_refresh_token(&self, token: &str, user_data: &Value) -> Result<(), JwtError> {
547        let expiry = (self.time_func)()
548            + chrono::Duration::from_std(self.refresh_token_timeout)
549                .unwrap_or_else(|_| chrono::Duration::days(30));
550        self.refresh_token_store
551            .set(token, user_data.clone(), expiry)
552            .await
553    }
554
555    /// Validate a refresh token and return its associated user data.
556    async fn validate_refresh_token(&self, token: &str) -> Result<Value, JwtError> {
557        self.refresh_token_store
558            .get(token)
559            .await
560            .map_err(|e| match e {
561                JwtError::RefreshTokenNotFound => JwtError::InvalidRefreshToken,
562                other => other,
563            })
564    }
565
566    /// Revoke (delete) a refresh token from storage.
567    async fn revoke_refresh_token(&self, token: &str) -> Result<(), JwtError> {
568        self.refresh_token_store.delete(token).await
569    }
570
571    /// Generate a complete token pair (access + refresh) and store the refresh
572    /// token. Mirrors [`TokenGenerator`](https://github.com/LdDl/echo-jwt/blob/master/auth_jwt.go)
573    /// from the Go implementation.
574    pub async fn token_generator(&self, data: &Value) -> Result<Token, JwtError> {
575        let (access_token, expire) = self.generate_access_token(data)?;
576        let refresh_token = self.generate_refresh_token()?;
577
578        self.store_refresh_token(&refresh_token, data).await?;
579
580        let now = (self.time_func)();
581        Ok(Token {
582            access_token,
583            token_type: "Bearer".to_string(),
584            refresh_token: Some(refresh_token),
585            expires_at: expire.timestamp(),
586            created_at: now.timestamp(),
587        })
588    }
589
590    /// Generate a new token pair and revoke the old refresh token (rotation).
591    pub async fn token_generator_with_revocation(
592        &self,
593        data: &Value,
594        old_refresh_token: &str,
595    ) -> Result<Token, JwtError> {
596        let token_pair = self.token_generator(data).await?;
597
598        // Revoke old token; ignore "not found" errors
599        if let Err(e) = self.revoke_refresh_token(old_refresh_token).await {
600            if !matches!(e, JwtError::RefreshTokenNotFound) {
601                return Err(e);
602            }
603        }
604
605        Ok(token_pair)
606    }
607
608    /// Parse and validate a JWT from the request according to `token_lookup`.
609    pub fn parse_token_from_request(
610        &self,
611        req: &HttpRequest,
612    ) -> Result<TokenData<Value>, JwtError> {
613        let token_str = self.extract_token_string(req)?;
614
615        // Store the raw token string in request extensions
616        req.extensions_mut()
617            .insert(JwtTokenString(token_str.clone()));
618
619        self.parse_token_string(&token_str)
620    }
621
622    /// Parse a raw JWT string and return its decoded data.
623    pub fn parse_token_string(&self, token: &str) -> Result<TokenData<Value>, JwtError> {
624        let alg = self.algorithm()?;
625
626        if let Some(ref kf) = self.key_func {
627            // Decode header first to pass to key_func
628            let header = jsonwebtoken::decode_header(token)
629                .map_err(|e| JwtError::TokenParsing(e.to_string()))?;
630            let dk = kf(&header)?;
631            let mut validation = Validation::new(alg);
632            validation.validate_exp = true;
633            validation.validate_aud = false;
634            validation.required_spec_claims.clear();
635            return jsonwebtoken::decode::<Value>(token, &dk, &validation)
636                .map_err(|e| JwtError::TokenParsing(e.to_string()));
637        }
638
639        let decoding_key = self
640            .decoding_key
641            .as_ref()
642            .ok_or(JwtError::MissingSecretKey)?;
643
644        let mut validation = Validation::new(alg);
645        validation.validate_exp = true;
646        validation.validate_aud = false;
647        validation.required_spec_claims.clear();
648
649        jsonwebtoken::decode::<Value>(token, decoding_key, &validation)
650            .map_err(|e| JwtError::TokenParsing(e.to_string()))
651    }
652
653    /// Walk `token_lookup` to find the first available token string in the request.
654    fn extract_token_string(&self, req: &HttpRequest) -> Result<String, JwtError> {
655        let methods: Vec<&str> = self.token_lookup.split(',').collect();
656        let mut last_err: Option<JwtError> = None;
657
658        for method in methods {
659            let parts: Vec<&str> = method.trim().splitn(2, ':').collect();
660            if parts.len() != 2 {
661                continue;
662            }
663            let source = parts[0].trim();
664            let name = parts[1].trim();
665
666            let result = match source {
667                "header" => self.jwt_from_header(req, name),
668                "query" => self.jwt_from_query(req, name),
669                "cookie" => self.jwt_from_cookie(req, name),
670                "param" => self.jwt_from_param(req, name),
671                "form" => self.jwt_from_form(req, name),
672                _ => continue,
673            };
674
675            match result {
676                Ok(t) if !t.is_empty() => return Ok(t),
677                Ok(_) => {}
678                Err(e) => {
679                    last_err = Some(e);
680                }
681            }
682        }
683
684        Err(last_err.unwrap_or(JwtError::TokenExtraction(
685            "no token found in request".to_string(),
686        )))
687    }
688
689    fn jwt_from_header(&self, req: &HttpRequest, key: &str) -> Result<String, JwtError> {
690        let auth_header = req
691            .headers()
692            .get(key)
693            .and_then(|v| v.to_str().ok())
694            .unwrap_or("");
695
696        if auth_header.is_empty() {
697            return Err(JwtError::EmptyAuthHeader);
698        }
699
700        let parts: Vec<&str> = auth_header.splitn(2, ' ').collect();
701        if parts.len() != 2 || parts[0] != self.token_head_name {
702            return Err(JwtError::InvalidAuthHeader);
703        }
704
705        Ok(parts[1].to_string())
706    }
707
708    fn jwt_from_query(&self, req: &HttpRequest, key: &str) -> Result<String, JwtError> {
709        let qs = req.query_string();
710        // Simple query-string parser
711        for pair in qs.split('&') {
712            let mut kv = pair.splitn(2, '=');
713            if let (Some(k), Some(v)) = (kv.next(), kv.next()) {
714                if k == key && !v.is_empty() {
715                    return Ok(v.to_string());
716                }
717            }
718        }
719        Err(JwtError::EmptyQueryToken)
720    }
721
722    fn jwt_from_cookie(&self, req: &HttpRequest, key: &str) -> Result<String, JwtError> {
723        req.cookie(key)
724            .map(|c| c.value().to_string())
725            .filter(|v| !v.is_empty())
726            .ok_or(JwtError::EmptyCookieToken)
727    }
728
729    fn jwt_from_param(&self, req: &HttpRequest, key: &str) -> Result<String, JwtError> {
730        let val = req.match_info().get(key).unwrap_or("");
731        if val.is_empty() {
732            return Err(JwtError::EmptyParamToken);
733        }
734        Ok(val.to_string())
735    }
736
737    fn jwt_from_form(&self, _req: &HttpRequest, _key: &str) -> Result<String, JwtError> {
738        // Form extraction requires body access which is not available from
739        // HttpRequest alone. This source is best-effort and typically handled
740        // via a pre-parsed body in the handler. Return empty for middleware path.
741        Err(JwtError::EmptyParamToken)
742    }
743
744    /// Get JWT claims from the request by parsing the token.
745    fn get_claims_from_jwt(&self, req: &HttpRequest) -> Result<HashMap<String, Value>, JwtError> {
746        let token_data = self.parse_token_from_request(req)?;
747
748        // Token string is already stored in extensions by
749        // parse_token_from_request; nothing extra needed for
750        // send_authorization here - the middleware layer reads it later.
751
752        let claims_map = match token_data.claims {
753            Value::Object(map) => map.into_iter().collect(),
754            _ => HashMap::new(),
755        };
756
757        Ok(claims_map)
758    }
759
760    /// Inner implementation: validate token, set identity, check authorizer.
761    fn middleware_impl(&self, req: &HttpRequest) -> Result<(), JwtError> {
762        let claims = self
763            .get_claims_from_jwt(req)
764            .map_err(|e| JwtError::TokenParsing(e.to_string()))?;
765
766        // exp is required (backwards-compat with gin-jwt)
767        if !claims.contains_key("exp") {
768            return Err(JwtError::TokenExtraction(
769                JwtError::MissingExpField.to_string(),
770            ));
771        }
772
773        req.extensions_mut().insert(JwtPayload(claims));
774
775        let identity = (self.identity_handler)(req);
776        if let Some(ref id) = identity {
777            req.extensions_mut().insert(JwtIdentity(id.clone()));
778        }
779
780        let auth_data = identity.unwrap_or(Value::Null);
781        if !(self.authorizer)(req, &auth_data) {
782            return Err(JwtError::Forbidden);
783        }
784
785        Ok(())
786    }
787
788    /// Build a default "unauthorized" `HttpResponse` with `WWW-Authenticate` header.
789    fn unauthorized_response(&self, req: &HttpRequest, code: u16, message: &str) -> HttpResponse {
790        let mut resp = (self.unauthorized)(req, code, message);
791        resp.headers_mut().insert(
792            header::WWW_AUTHENTICATE,
793            format!("Bearer realm=\"{}\"", self.realm).parse().unwrap(),
794        );
795        resp
796    }
797
798    /// Handle a middleware error when no custom `ErrorHandler` is set.
799    fn handle_middleware_error(&self, req: &HttpRequest, err: &JwtError) -> HttpResponse {
800        match err {
801            JwtError::Forbidden => {
802                let msg = (self.http_status_message_func)(req, &JwtError::Forbidden);
803                self.unauthorized_response(req, 403, &msg)
804            }
805            JwtError::TokenParsing(inner) => self.handle_token_error(req, inner),
806            JwtError::TokenExtraction(inner) => {
807                let msg = inner.clone();
808                self.unauthorized_response(req, 400, &msg)
809            }
810            other => {
811                let msg = (self.http_status_message_func)(req, other);
812                self.unauthorized_response(req, 401, &msg)
813            }
814        }
815    }
816
817    fn handle_token_error(&self, req: &HttpRequest, detail: &str) -> HttpResponse {
818        let lower = detail.to_lowercase();
819        if lower.contains("expired") {
820            let msg = (self.http_status_message_func)(req, &JwtError::ExpiredToken);
821            self.unauthorized_response(req, 401, &msg)
822        } else if lower.contains("exp") && lower.contains("invalid") {
823            let msg = (self.http_status_message_func)(req, &JwtError::WrongFormatOfExp);
824            self.unauthorized_response(req, 400, &msg)
825        } else if lower.contains("exp") && lower.contains("required") {
826            let msg = (self.http_status_message_func)(req, &JwtError::MissingExpField);
827            self.unauthorized_response(req, 400, &msg)
828        } else {
829            let err = JwtError::TokenParsing(detail.to_string());
830            let msg = (self.http_status_message_func)(req, &err);
831            self.unauthorized_response(req, 401, &msg)
832        }
833    }
834
835    /// Set the access-token cookie on the response builder.
836    pub fn set_cookie(builder: &mut HttpResponseBuilder, config: &CookieConfig, value: &str) {
837        let mut cookie = Cookie::build(config.name.clone(), value.to_string())
838            .path("/")
839            .max_age(actix_web::cookie::time::Duration::seconds(
840                config.max_age.as_secs() as i64,
841            ))
842            .secure(config.secure)
843            .http_only(config.http_only)
844            .same_site(config.same_site)
845            .finish();
846
847        if let Some(ref domain) = config.domain {
848            cookie.set_domain(domain.clone());
849        }
850
851        builder.cookie(cookie);
852    }
853
854    /// Build a `CookieConfig` for the access token from the middleware settings.
855    pub fn access_cookie_config(&self) -> CookieConfig {
856        CookieConfig {
857            name: self.cookie_name.clone(),
858            max_age: self.cookie_max_age,
859            secure: self.secure_cookie,
860            http_only: self.cookie_http_only,
861            domain: self.cookie_domain.clone(),
862            same_site: self.cookie_same_site,
863        }
864    }
865
866    /// Build a `CookieConfig` for the refresh token from the middleware settings.
867    pub fn refresh_cookie_config(&self) -> CookieConfig {
868        CookieConfig {
869            name: self.refresh_token_cookie_name.clone(),
870            max_age: self.refresh_token_timeout,
871            secure: true,    // always secure for refresh tokens
872            http_only: true, // always httpOnly for security
873            domain: self.cookie_domain.clone(),
874            same_site: self.cookie_same_site,
875        }
876    }
877
878    /// Append a Set-Cookie header directly to an already-built response's
879    /// `HeaderMap`.  Used when the response is created by a callback
880    /// (`login_response`, `refresh_response`) and cookies must be added
881    /// afterwards.
882    fn append_cookie(
883        headers: &mut actix_web::http::header::HeaderMap,
884        config: &CookieConfig,
885        value: &str,
886    ) {
887        let mut cookie = Cookie::build(config.name.clone(), value.to_string())
888            .path("/")
889            .max_age(actix_web::cookie::time::Duration::seconds(
890                config.max_age.as_secs() as i64,
891            ))
892            .secure(config.secure)
893            .http_only(config.http_only)
894            .same_site(config.same_site)
895            .finish();
896
897        if let Some(ref domain) = config.domain {
898            cookie.set_domain(domain.clone());
899        }
900
901        headers.append(header::SET_COOKIE, cookie.to_string().parse().unwrap());
902    }
903
904    /// Append a "delete" Set-Cookie header (MaxAge = -1) directly to an
905    /// already-built response's `HeaderMap`.
906    fn append_delete_cookie(
907        headers: &mut actix_web::http::header::HeaderMap,
908        config: &CookieConfig,
909    ) {
910        let mut cookie = Cookie::build(config.name.clone(), "")
911            .path("/")
912            .max_age(actix_web::cookie::time::Duration::seconds(-1))
913            .secure(config.secure)
914            .http_only(config.http_only)
915            .same_site(config.same_site)
916            .finish();
917
918        if let Some(ref domain) = config.domain {
919            cookie.set_domain(domain.clone());
920        }
921
922        headers.append(header::SET_COOKIE, cookie.to_string().parse().unwrap());
923    }
924
925    /// Append a "delete" cookie (MaxAge = -1) to the response builder.
926    pub fn delete_cookie(builder: &mut HttpResponseBuilder, config: &CookieConfig) {
927        let mut cookie = Cookie::build(config.name.clone(), "")
928            .path("/")
929            .max_age(actix_web::cookie::time::Duration::seconds(-1))
930            .secure(config.secure)
931            .http_only(config.http_only)
932            .same_site(config.same_site)
933            .finish();
934
935        if let Some(ref domain) = config.domain {
936            cookie.set_domain(domain.clone());
937        }
938
939        builder.cookie(cookie);
940    }
941
942    /// Login handler. Expects JSON body parsed by the `authenticator` callback.
943    ///
944    /// Usage with `web::Data<Arc<ActixJwtMiddleware>>`:
945    /// ```ignore
946    /// async fn login(
947    ///     jwt: web::Data<Arc<ActixJwtMiddleware>>,
948    ///     req: HttpRequest,
949    ///     body: web::Bytes,
950    /// ) -> HttpResponse {
951    ///     jwt.login_handler(&req, &body).await
952    /// }
953    /// ```
954    pub async fn login_handler(&self, req: &HttpRequest, body: &[u8]) -> HttpResponse {
955        let authenticator = match self.authenticator {
956            Some(ref auth) => auth,
957            None => {
958                let msg = (self.http_status_message_func)(req, &JwtError::MissingAuthenticator);
959                return self.unauthorized_response(req, 500, &msg);
960            }
961        };
962
963        let data = match authenticator(req, body).await {
964            Ok(d) => d,
965            Err(e) => {
966                let msg = (self.http_status_message_func)(req, &e);
967                return self.unauthorized_response(req, 401, &msg);
968            }
969        };
970
971        let token_pair = match self.token_generator(&data).await {
972            Ok(t) => t,
973            Err(_) => {
974                let msg = (self.http_status_message_func)(req, &JwtError::FailedTokenCreation);
975                return self.unauthorized_response(req, 500, &msg);
976            }
977        };
978
979        let mut resp = (self.login_response)(req, &token_pair);
980
981        if self.send_cookie {
982            Self::append_cookie(
983                resp.headers_mut(),
984                &self.access_cookie_config(),
985                &token_pair.access_token,
986            );
987            if let Some(ref rt) = token_pair.refresh_token {
988                Self::append_cookie(resp.headers_mut(), &self.refresh_cookie_config(), rt);
989            }
990        }
991
992        resp
993    }
994
995    /// Extract a refresh token from the request (cookie, form, or JSON body).
996    pub fn extract_refresh_token(&self, req: &HttpRequest, body: &[u8]) -> Option<String> {
997        // 1. Try cookie
998        if let Some(cookie) = req.cookie(&self.refresh_token_cookie_name) {
999            let val = cookie.value().to_string();
1000            if !val.is_empty() {
1001                return Some(val);
1002            }
1003        }
1004
1005        // 2. Try JSON or form body
1006        let content_type = req
1007            .headers()
1008            .get(header::CONTENT_TYPE)
1009            .and_then(|v| v.to_str().ok())
1010            .unwrap_or("");
1011
1012        if content_type.contains("application/x-www-form-urlencoded")
1013            || content_type.contains("multipart/form-data")
1014        {
1015            // Attempt to parse as form-urlencoded
1016            let body_str = std::str::from_utf8(body).unwrap_or("");
1017            for pair in body_str.split('&') {
1018                let mut kv = pair.splitn(2, '=');
1019                if let (Some(k), Some(v)) = (kv.next(), kv.next()) {
1020                    if k == "refresh_token" && !v.is_empty() {
1021                        return Some(v.to_string());
1022                    }
1023                }
1024            }
1025        } else if content_type.contains("application/json") {
1026            #[derive(serde::Deserialize)]
1027            struct RefreshBody {
1028                refresh_token: Option<String>,
1029            }
1030            if let Ok(parsed) = serde_json::from_slice::<RefreshBody>(body) {
1031                if let Some(rt) = parsed.refresh_token {
1032                    if !rt.is_empty() {
1033                        return Some(rt);
1034                    }
1035                }
1036            }
1037        }
1038
1039        None
1040    }
1041
1042    /// Logout handler. Revokes the refresh token and clears cookies.
1043    pub async fn logout_handler(&self, req: &HttpRequest, body: &[u8]) -> HttpResponse {
1044        // Try to extract claims for the LogoutResponse callback
1045        if let Ok(claims) = self.get_claims_from_jwt(req) {
1046            req.extensions_mut().insert(JwtPayload(claims));
1047            let identity = (self.identity_handler)(req);
1048            if let Some(ref id) = identity {
1049                req.extensions_mut().insert(JwtIdentity(id.clone()));
1050            }
1051        }
1052
1053        // Revoke refresh token
1054        if let Some(ref rt) = self.extract_refresh_token(req, body) {
1055            if let Err(e) = self.revoke_refresh_token(rt).await {
1056                warn!("Failed to revoke refresh token on logout: {}", e);
1057            }
1058        }
1059
1060        let mut resp = (self.logout_response)(req);
1061
1062        if self.send_cookie {
1063            Self::append_delete_cookie(resp.headers_mut(), &self.access_cookie_config());
1064            Self::append_delete_cookie(resp.headers_mut(), &self.refresh_cookie_config());
1065        }
1066
1067        resp
1068    }
1069
1070    /// Refresh handler. Validates old refresh token, generates new token pair,
1071    /// revokes old refresh token (rotation).
1072    pub async fn refresh_handler(&self, req: &HttpRequest, body: &[u8]) -> HttpResponse {
1073        let refresh_token = match self.extract_refresh_token(req, body) {
1074            Some(rt) => rt,
1075            None => {
1076                let msg = (self.http_status_message_func)(req, &JwtError::MissingRefreshToken);
1077                return self.unauthorized_response(req, 400, &msg);
1078            }
1079        };
1080
1081        let user_data = match self.validate_refresh_token(&refresh_token).await {
1082            Ok(d) => d,
1083            Err(e) => {
1084                let msg = (self.http_status_message_func)(req, &e);
1085                return self.unauthorized_response(req, 401, &msg);
1086            }
1087        };
1088
1089        let token_pair = match self
1090            .token_generator_with_revocation(&user_data, &refresh_token)
1091            .await
1092        {
1093            Ok(t) => t,
1094            Err(e) => {
1095                let msg = (self.http_status_message_func)(req, &e);
1096                return self.unauthorized_response(req, 500, &msg);
1097            }
1098        };
1099
1100        let mut resp = (self.refresh_response)(req, &token_pair);
1101
1102        if self.send_cookie {
1103            Self::append_cookie(
1104                resp.headers_mut(),
1105                &self.access_cookie_config(),
1106                &token_pair.access_token,
1107            );
1108            if let Some(ref rt) = token_pair.refresh_token {
1109                Self::append_cookie(resp.headers_mut(), &self.refresh_cookie_config(), rt);
1110            }
1111        }
1112
1113        resp
1114    }
1115
1116    /// Builds an [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-5.1)
1117    /// token response map containing `access_token`, `token_type`,
1118    /// `expires_in` and (optionally) `refresh_token`.
1119    pub fn generate_token_response(token: &Token) -> serde_json::Map<String, Value> {
1120        let mut map = serde_json::Map::new();
1121        map.insert(
1122            "access_token".into(),
1123            Value::String(token.access_token.clone()),
1124        );
1125        map.insert("token_type".into(), Value::String(token.token_type.clone()));
1126        map.insert(
1127            "expires_in".into(),
1128            Value::Number(token.expires_in().into()),
1129        );
1130
1131        if let Some(ref rt) = token.refresh_token {
1132            map.insert("refresh_token".into(), Value::String(rt.clone()));
1133        }
1134
1135        map
1136    }
1137
1138    /// Same as `generate_token_response` but returns a `Value` for direct
1139    /// serialisation (used by default response callbacks).
1140    fn generate_token_response_static(token: &Token) -> Value {
1141        let map = Self::generate_token_response(token);
1142        Value::Object(map)
1143    }
1144
1145    /// Creates an actix-web `Transform` that can be passed to `.wrap()`.
1146    ///
1147    /// The middleware must be wrapped in `Arc` before calling this method.
1148    pub fn middleware(self: &Arc<Self>) -> JwtAuth {
1149        JwtAuth {
1150            inner: self.clone(),
1151        }
1152    }
1153}
1154
1155impl Default for ActixJwtMiddleware {
1156    fn default() -> Self {
1157        Self::new()
1158    }
1159}
1160
1161/// Describes cookie parameters for either access or refresh tokens.
1162///
1163/// Constructed internally by
1164/// [`ActixJwtMiddleware::access_cookie_config`] /
1165/// [`ActixJwtMiddleware::refresh_cookie_config`].
1166pub struct CookieConfig {
1167    /// Cookie name.
1168    pub name: String,
1169    /// `Max-Age` value.
1170    pub max_age: Duration,
1171    /// `Secure` flag.
1172    pub secure: bool,
1173    /// `HttpOnly` flag.
1174    pub http_only: bool,
1175    /// Optional `Domain` attribute.
1176    pub domain: Option<String>,
1177    /// `SameSite` attribute.
1178    pub same_site: SameSite,
1179}
1180
1181/// Extracts JWT claims from [`HttpRequest`] extensions.
1182///
1183/// Returns an empty map if the middleware has not yet validated a token for
1184/// this request.
1185///
1186/// # Examples
1187///
1188/// ```rust,no_run
1189/// use actix_web::{HttpRequest, HttpResponse};
1190/// use actix_jwt::extract_claims;
1191///
1192/// async fn handler(req: HttpRequest) -> HttpResponse {
1193///     let claims = extract_claims(&req);
1194///     HttpResponse::Ok().json(claims)
1195/// }
1196/// ```
1197pub fn extract_claims(req: &HttpRequest) -> HashMap<String, Value> {
1198    req.extensions()
1199        .get::<JwtPayload>()
1200        .map(|p| p.0.clone())
1201        .unwrap_or_default()
1202}
1203
1204/// Extracts the raw JWT token string from [`HttpRequest`] extensions.
1205///
1206/// Returns `None` if the middleware has not processed the request yet.
1207pub fn get_token(req: &HttpRequest) -> Option<String> {
1208    req.extensions()
1209        .get::<JwtTokenString>()
1210        .map(|t| t.0.clone())
1211}
1212
1213/// Extracts the identity value from [`HttpRequest`] extensions.
1214///
1215/// Returns `None` if the middleware has not processed the request yet or no
1216/// identity was resolved.
1217pub fn get_identity(req: &HttpRequest) -> Option<Value> {
1218    req.extensions().get::<JwtIdentity>().map(|i| i.0.clone())
1219}
1220
1221/// [`Transform`] factory produced by
1222/// [`ActixJwtMiddleware::middleware`].
1223///
1224/// You do not need to construct this directly - use
1225/// `jwt.middleware()` instead.
1226pub struct JwtAuth {
1227    inner: Arc<ActixJwtMiddleware>,
1228}
1229
1230impl<S, B> Transform<S, ServiceRequest> for JwtAuth
1231where
1232    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
1233    B: 'static,
1234{
1235    type Response = ServiceResponse<actix_web::body::EitherBody<B>>;
1236    type Error = actix_web::Error;
1237    type Transform = JwtAuthMiddleware<S>;
1238    type InitError = ();
1239    type Future = Ready<Result<Self::Transform, Self::InitError>>;
1240
1241    fn new_transform(&self, service: S) -> Self::Future {
1242        ready(Ok(JwtAuthMiddleware {
1243            service: Arc::new(service),
1244            inner: self.inner.clone(),
1245        }))
1246    }
1247}
1248
1249/// Per-request middleware service created by [`JwtAuth`].
1250pub struct JwtAuthMiddleware<S> {
1251    service: Arc<S>,
1252    inner: Arc<ActixJwtMiddleware>,
1253}
1254
1255impl<S, B> Service<ServiceRequest> for JwtAuthMiddleware<S>
1256where
1257    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error> + 'static,
1258    B: 'static,
1259{
1260    type Response = ServiceResponse<actix_web::body::EitherBody<B>>;
1261    type Error = actix_web::Error;
1262    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
1263
1264    fn poll_ready(
1265        &self,
1266        ctx: &mut std::task::Context<'_>,
1267    ) -> std::task::Poll<Result<(), Self::Error>> {
1268        self.service.poll_ready(ctx)
1269    }
1270
1271    fn call(&self, req: ServiceRequest) -> Self::Future {
1272        let mw = self.inner.clone();
1273        let service = self.service.clone();
1274
1275        Box::pin(async move {
1276            // 1. Skipper
1277            if let Some(ref skipper) = mw.skipper {
1278                if skipper(&req) {
1279                    let res = service.call(req).await?;
1280                    return Ok(res.map_into_left_body());
1281                }
1282            }
1283
1284            // 2. BeforeFunc
1285            if let Some(ref bf) = mw.before_func {
1286                bf(&req);
1287            }
1288
1289            // 3. Run middleware logic
1290            //
1291            // IMPORTANT: We must NOT hold an `HttpRequest` clone while calling
1292            // `service.call(req)` because actix-web's `Rc::get_mut` on the
1293            // inner request data will panic if the refcount > 1. Instead, we
1294            // borrow `req.request()` only within a limited scope.
1295            let mw_result = mw.middleware_impl(req.request());
1296
1297            if let Err(err) = mw_result {
1298                // ErrorHandler
1299                if let Some(ref eh) = mw.error_handler {
1300                    let maybe_err = eh(req.request(), err);
1301                    if maybe_err.is_none() && mw.continue_on_ignored_error {
1302                        // Extensions are already on the shared HttpRequest
1303                        // inner, so the ServiceRequest can see them without
1304                        // an explicit copy.
1305                        let res = service.call(req).await?;
1306                        return Ok(res.map_into_left_body());
1307                    }
1308                    if let Some(e) = maybe_err {
1309                        let resp = mw.handle_middleware_error(req.request(), &e);
1310                        return Ok(req.into_response(resp).map_into_right_body());
1311                    }
1312                    // ErrorHandler returned None but ContinueOnIgnoredError is false
1313                    return Ok(req
1314                        .into_response(HttpResponse::Ok().finish())
1315                        .map_into_right_body());
1316                }
1317
1318                // No ErrorHandler - default unauthorized
1319                let resp = mw.handle_middleware_error(req.request(), &err);
1320                return Ok(req.into_response(resp).map_into_right_body());
1321            }
1322
1323            // 4. SuccessHandler
1324            if let Some(ref sh) = mw.success_handler {
1325                if let Err(e) = sh(req.request()) {
1326                    let resp = mw.handle_middleware_error(req.request(), &e);
1327                    return Ok(req.into_response(resp).map_into_right_body());
1328                }
1329            }
1330
1331            // 5. Send authorization header if configured
1332            let send_auth = if mw.send_authorization {
1333                let ext = req.extensions();
1334                ext.get::<JwtTokenString>()
1335                    .map(|t| format!("{} {}", mw.token_head_name, t.0))
1336            } else {
1337                None
1338            };
1339
1340            // 6. Call inner service
1341            let mut res = service.call(req).await?;
1342
1343            if let Some(val) = send_auth {
1344                res.headers_mut()
1345                    .insert(header::AUTHORIZATION, val.parse().unwrap());
1346            }
1347
1348            Ok(res.map_into_left_body())
1349        })
1350    }
1351}