Skip to main content

webgates_codecs/
jwt.rs

1//! JWT claim types, codecs, validation helpers, and JWKS support.
2//!
3//! This module contains the main JWT-facing API of `webgates-codecs`.
4//!
5//! It provides:
6//! - registered JWT claims via [`RegisteredClaims`]
7//! - combined application and registered claims via [`JwtClaims`]
8//! - a configurable JWT codec via [`JsonWebToken`] and [`JsonWebTokenOptions`]
9//! - validation helpers in [`validation_service`] and [`validation_result`]
10//! - JWKS helpers in [`jwks`]
11//!
12//! The implementation is framework-agnostic and depends only on shared types
13//! from `webgates-core` plus the codec abstractions from this crate.
14//!
15//! Prefer the canonical module-owned public paths for validation helpers:
16//! - [`validation_service::JwtValidationService`]
17//! - [`validation_result::JwtValidationResult`]
18
19use crate::errors::{JwtError, JwtOperation};
20use crate::jwt::authority::JwtAuthority;
21use crate::{Codec, Error, Result};
22
23use std::collections::HashMap;
24use std::collections::HashSet;
25use std::marker::PhantomData;
26use std::path::{Path, PathBuf};
27use std::sync::RwLock;
28
29use chrono::Utc;
30use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode_header};
31use p384::elliptic_curve::rand_core::OsRng;
32use p384::pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding};
33use serde::{Deserialize, Serialize, de::DeserializeOwned};
34use serde_with::skip_serializing_none;
35use uuid::Uuid;
36
37pub mod authority;
38pub mod jwks;
39pub mod remote_verifier;
40pub mod validation_result;
41pub mod validation_service;
42
43fn validation_with_es384_only() -> Validation {
44    let mut validation = Validation::new(Algorithm::ES384);
45    validation.algorithms = vec![Algorithm::ES384];
46    validation
47}
48
49fn canonical_es384_header_with_kid(kid: &str) -> Header {
50    let mut header = Header::new(Algorithm::ES384);
51    header.typ = Some("JWT".to_string());
52    header.kid = Some(kid.to_string());
53    header
54}
55
56/// File paths for an ES384 key pair managed on disk.
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct Es384KeyPairPaths {
59    private_key_path: PathBuf,
60    public_key_path: PathBuf,
61}
62
63impl Es384KeyPairPaths {
64    /// Creates key pair paths from explicit private and public key locations.
65    pub fn new(private_key_path: impl Into<PathBuf>, public_key_path: impl Into<PathBuf>) -> Self {
66        Self {
67            private_key_path: private_key_path.into(),
68            public_key_path: public_key_path.into(),
69        }
70    }
71
72    /// Returns the private key file path.
73    pub fn private_key_path(&self) -> &Path {
74        &self.private_key_path
75    }
76
77    /// Returns the public key file path.
78    pub fn public_key_path(&self) -> &Path {
79        &self.public_key_path
80    }
81
82    /// Returns whether both key files currently exist.
83    pub fn both_exist(&self) -> bool {
84        self.private_key_path.is_file() && self.public_key_path.is_file()
85    }
86}
87
88/// In-memory ES384 key material loaded from disk or generated on startup.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct Es384KeyPair {
91    paths: Es384KeyPairPaths,
92    private_key_pem: Vec<u8>,
93    public_key_pem: Vec<u8>,
94}
95
96impl Es384KeyPair {
97    /// Creates an in-memory key pair from explicit paths and PEM bytes.
98    pub fn new(
99        paths: Es384KeyPairPaths,
100        private_key_pem: impl Into<Vec<u8>>,
101        public_key_pem: impl Into<Vec<u8>>,
102    ) -> Self {
103        Self {
104            paths,
105            private_key_pem: private_key_pem.into(),
106            public_key_pem: public_key_pem.into(),
107        }
108    }
109
110    /// Returns the file paths associated with this key pair.
111    pub fn paths(&self) -> &Es384KeyPairPaths {
112        &self.paths
113    }
114
115    /// Returns the private key file path.
116    pub fn private_key_path(&self) -> &Path {
117        self.paths.private_key_path()
118    }
119
120    /// Returns the public key file path.
121    pub fn public_key_path(&self) -> &Path {
122        self.paths.public_key_path()
123    }
124
125    /// Returns the PEM-encoded private key bytes.
126    pub fn private_key_pem(&self) -> &[u8] {
127        &self.private_key_pem
128    }
129
130    /// Returns the PEM-encoded public key bytes.
131    pub fn public_key_pem(&self) -> &[u8] {
132        &self.public_key_pem
133    }
134
135    /// Converts the loaded key pair into JWT codec options.
136    pub fn to_jwt_options(&self) -> Result<JsonWebTokenOptions> {
137        JsonWebTokenOptions::from_es384_pem(&self.private_key_pem, &self.public_key_pem)
138    }
139
140    /// Builds a JWT codec directly from this loaded key pair.
141    pub fn to_codec<P>(&self) -> Result<JsonWebToken<P>> {
142        Ok(JsonWebToken::new_with_options(self.to_jwt_options()?))
143    }
144
145    /// Builds a JWT authority directly from this loaded key pair.
146    pub fn to_authority<P>(&self) -> Result<JwtAuthority<P>>
147    where
148        P: Serialize + DeserializeOwned + Clone,
149    {
150        JwtAuthority::from_es384_pem(&self.private_key_pem, &self.public_key_pem)
151    }
152}
153
154/// Loads or initializes an ES384 key pair from the filesystem.
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct Es384KeyPairLoader {
157    paths: Es384KeyPairPaths,
158}
159
160impl Es384KeyPairLoader {
161    /// Creates a new loader for the given private and public key paths.
162    pub fn new(private_key_path: impl Into<PathBuf>, public_key_path: impl Into<PathBuf>) -> Self {
163        Self {
164            paths: Es384KeyPairPaths::new(private_key_path, public_key_path),
165        }
166    }
167
168    /// Returns the configured key paths.
169    pub fn paths(&self) -> &Es384KeyPairPaths {
170        &self.paths
171    }
172
173    /// Creates the key pair if missing and then loads both PEM files.
174    ///
175    /// If both files already exist, they are reused unchanged. If neither file
176    /// exists, a new ES384 key pair is generated, written to disk, and loaded.
177    ///
178    /// # Errors
179    ///
180    /// Returns an error when only one key file exists, when directories or
181    /// files cannot be created or read, or when the resulting PEM bytes are not
182    /// a valid ES384 key pair.
183    pub async fn initialize_if_required(&self) -> Result<Es384KeyPair> {
184        let private_exists = self.paths.private_key_path.is_file();
185        let public_exists = self.paths.public_key_path.is_file();
186
187        match (private_exists, public_exists) {
188            (true, true) => self.load().await,
189            (false, false) => {
190                self.create_parent_directories().await?;
191                let (private_key_pem, public_key_pem) = generate_es384_key_pair_pem()?;
192                tokio::fs::write(&self.paths.private_key_path, &private_key_pem)
193                    .await
194                    .map_err(|error| {
195                        Error::Jwt(JwtError::processing(
196                            JwtOperation::Encode,
197                            format!(
198                                "failed to write ES384 private key `{}`: {error}",
199                                self.paths.private_key_path.display()
200                            ),
201                        ))
202                    })?;
203                tokio::fs::write(&self.paths.public_key_path, &public_key_pem)
204                    .await
205                    .map_err(|error| {
206                        Error::Jwt(JwtError::processing(
207                            JwtOperation::Encode,
208                            format!(
209                                "failed to write ES384 public key `{}`: {error}",
210                                self.paths.public_key_path.display()
211                            ),
212                        ))
213                    })?;
214                self.validate_loaded_pair(Es384KeyPair::new(
215                    self.paths.clone(),
216                    private_key_pem,
217                    public_key_pem,
218                ))
219            }
220            _ => Err(Error::Jwt(JwtError::processing(
221                JwtOperation::Encode,
222                format!(
223                    "ES384 key initialization requires both key files to exist or neither to exist; private=`{}`, public=`{}`",
224                    self.paths.private_key_path.display(),
225                    self.paths.public_key_path.display()
226                ),
227            ))),
228        }
229    }
230
231    /// Loads both PEM files from disk.
232    ///
233    /// # Errors
234    ///
235    /// Returns an error if either file cannot be read or if the loaded bytes do
236    /// not form a valid ES384 key pair.
237    pub async fn load(&self) -> Result<Es384KeyPair> {
238        let private_key_pem = tokio::fs::read(&self.paths.private_key_path)
239            .await
240            .map_err(|error| {
241                Error::Jwt(JwtError::processing(
242                    JwtOperation::Decode,
243                    format!(
244                        "failed to read ES384 private key `{}`: {error}",
245                        self.paths.private_key_path.display()
246                    ),
247                ))
248            })?;
249        let public_key_pem =
250            tokio::fs::read(&self.paths.public_key_path)
251                .await
252                .map_err(|error| {
253                    Error::Jwt(JwtError::processing(
254                        JwtOperation::Decode,
255                        format!(
256                            "failed to read ES384 public key `{}`: {error}",
257                            self.paths.public_key_path.display()
258                        ),
259                    ))
260                })?;
261
262        self.validate_loaded_pair(Es384KeyPair::new(
263            self.paths.clone(),
264            private_key_pem,
265            public_key_pem,
266        ))
267    }
268
269    async fn create_parent_directories(&self) -> Result<()> {
270        if let Some(parent) = self.paths.private_key_path.parent()
271            && !parent.as_os_str().is_empty()
272        {
273            tokio::fs::create_dir_all(parent).await.map_err(|error| {
274                Error::Jwt(JwtError::processing(
275                    JwtOperation::Encode,
276                    format!(
277                        "failed to create private key directory `{}`: {error}",
278                        parent.display()
279                    ),
280                ))
281            })?;
282        }
283
284        if let Some(parent) = self.paths.public_key_path.parent()
285            && !parent.as_os_str().is_empty()
286        {
287            tokio::fs::create_dir_all(parent).await.map_err(|error| {
288                Error::Jwt(JwtError::processing(
289                    JwtOperation::Encode,
290                    format!(
291                        "failed to create public key directory `{}`: {error}",
292                        parent.display()
293                    ),
294                ))
295            })?;
296        }
297
298        Ok(())
299    }
300
301    fn validate_loaded_pair(&self, key_pair: Es384KeyPair) -> Result<Es384KeyPair> {
302        key_pair.to_jwt_options()?;
303        Ok(key_pair)
304    }
305}
306
307fn generate_es384_key_pair_pem() -> Result<(Vec<u8>, Vec<u8>)> {
308    let signing_key = p384::ecdsa::SigningKey::random(&mut OsRng);
309    let verifying_key = signing_key.verifying_key();
310
311    let private_key_pem = signing_key
312        .to_pkcs8_pem(LineEnding::LF)
313        .map_err(|error| {
314            Error::Jwt(JwtError::processing(
315                JwtOperation::Encode,
316                format!("failed to encode ES384 private key as PKCS#8 PEM: {error}"),
317            ))
318        })?
319        .to_string()
320        .into_bytes();
321    let public_key_pem = verifying_key
322        .to_public_key_pem(LineEnding::LF)
323        .map_err(|error| {
324            Error::Jwt(JwtError::processing(
325                JwtOperation::Encode,
326                format!("failed to encode ES384 public key as PEM: {error}"),
327            ))
328        })?
329        .into_bytes();
330
331    Ok((private_key_pem, public_key_pem))
332}
333
334/// Registered claims defined by the JWT specification.
335///
336/// These are the standard metadata fields that travel alongside your
337/// application-specific payload in a JWT.
338#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
339#[skip_serializing_none]
340pub struct RegisteredClaims {
341    /// Issuer of the JWT.
342    #[serde(rename = "iss")]
343    pub issuer: String,
344    /// Subject of the JWT.
345    #[serde(rename = "sub")]
346    pub subject: Option<String>,
347    /// Recipient for which the JWT is intended.
348    #[serde(rename = "aud")]
349    pub audience: Option<HashSet<String>>,
350    /// Time after which the JWT expires.
351    #[serde(rename = "exp")]
352    pub expiration_time: u64,
353    /// Time before which the JWT must not be accepted for processing.
354    #[serde(rename = "nbf")]
355    pub not_before_time: Option<u64>,
356    /// Time at which the JWT was issued.
357    #[serde(rename = "iat")]
358    pub issued_at_time: u64,
359    /// Unique token identifier.
360    #[serde(rename = "jti")]
361    pub jwt_id: Option<String>,
362    /// Session identifier for session-backed tokens.
363    #[serde(rename = "sid")]
364    pub session_id: Option<String>,
365}
366
367impl RegisteredClaims {
368    /// Creates new registered claims and sets `issued_at_time` to `Utc::now()`.
369    pub fn new(issuer: &str, expiration_time: u64) -> Self {
370        // chrono::DateTime::timestamp() returns i64; use a checked conversion so a
371        // negative timestamp (clock correction, pre-epoch test date) does not wrap
372        // silently to a very large u64 and produce a malformed `iat` claim.
373        let issued_at_time = u64::try_from(Utc::now().timestamp()).unwrap_or(0);
374        Self {
375            issuer: issuer.to_string(),
376            subject: None,
377            audience: None,
378            expiration_time,
379            not_before_time: None,
380            issued_at_time,
381            jwt_id: Some(Uuid::now_v7().to_string()),
382            session_id: None,
383        }
384    }
385
386    /// Returns updated claims with an explicit session identifier.
387    #[must_use]
388    pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
389        self.session_id = Some(session_id.into());
390        self
391    }
392}
393
394/// Combined registered and application-specific JWT claims.
395///
396/// This is the main typed claim container used with [`JsonWebToken<T>`].
397#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
398pub struct JwtClaims<CustomClaims> {
399    /// Standard JWT registered claims.
400    #[serde(flatten)]
401    pub registered_claims: RegisteredClaims,
402    /// Application-specific claims.
403    #[serde(flatten)]
404    pub custom_claims: CustomClaims,
405}
406
407impl<CustomClaims> JwtClaims<CustomClaims> {
408    /// Creates a new combined claims value.
409    pub fn new(custom_claims: CustomClaims, registered_claims: RegisteredClaims) -> Self {
410        Self {
411            custom_claims,
412            registered_claims,
413        }
414    }
415
416    /// Returns `true` when the issuer matches `issuer`.
417    pub fn has_issuer(&self, issuer: &str) -> bool {
418        self.registered_claims.issuer == issuer
419    }
420}
421
422/// Options used to configure a [`JsonWebToken`] codec.
423///
424/// Use this when you need explicit control over signing keys, verification keys,
425/// key identifiers, or validation settings.
426#[derive(Debug, Clone)]
427pub struct JsonWebTokenOptions {
428    /// Key for ES384 encoding.
429    encoding_key: Option<EncodingKey>,
430    /// Canonical key id used when minting JWTs.
431    key_id: String,
432    /// Public verification keys indexed by `kid`.
433    decoding_keys_by_kid: HashMap<String, DecodingKey>,
434    /// Legacy fallback verification key when no `kid` was provided in the token.
435    fallback_decoding_key: Option<DecodingKey>,
436    /// Header used for encoding.
437    header: Header,
438    /// Validation options used during decoding.
439    validation: Validation,
440}
441
442const DEV_ES384_PRIVATE_KEY_PEM: &[u8] = br#"-----BEGIN PRIVATE KEY-----
443MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCFT7MfRqWZfNgVX/cH
444bxFTlPkBeCKqjsLkZXD/J3ZYHV1EtQksdrKtOzTr2hMs6pmhZANiAASyND9eQ5Qk
4457ZteSEPMpExbVJenRWwyobExJMb62mmp3eA7Fszy8uBbLj8HRB16y3QbLcTxCBoo
446ldBXfNFzM133OuTV2bBWXq5h34l+A0h4gU/odZ678LfAgnrRYMG4ZjU=
447-----END PRIVATE KEY-----
448"#;
449
450const DEV_ES384_PUBLIC_KEY_PEM: &[u8] = br#"-----BEGIN PUBLIC KEY-----
451MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEsjQ/XkOUJO2bXkhDzKRMW1SXp0VsMqGx
452MSTG+tppqd3gOxbM8vLgWy4/B0Qdest0Gy3E8QgaKJXQV3zRczNd9zrk1dmwVl6u
453Yd+JfgNIeIFP6HWeu/C3wIJ60WDBuGY1
454-----END PUBLIC KEY-----
455"#;
456
457impl Default for JsonWebTokenOptions {
458    /// Creates ES384 encoding and decoding keys from built-in development keys.
459    ///
460    /// This default is intended for tests and local development only. Production
461    /// deployments should provide explicit key material with
462    /// [`JsonWebTokenOptions::from_es384_pem`].
463    fn default() -> Self {
464        match Self::from_es384_pem(DEV_ES384_PRIVATE_KEY_PEM, DEV_ES384_PUBLIC_KEY_PEM) {
465            Ok(options) => options,
466            Err(error) => panic!("failed to initialize default ES384 JWT options: {error}"),
467        }
468    }
469}
470
471impl JsonWebTokenOptions {
472    /// Creates ES384 JWT options from PEM-encoded private and public keys.
473    ///
474    /// # Errors
475    ///
476    /// Returns a JWT processing error when either key cannot be parsed.
477    pub fn from_es384_pem(private_key_pem: &[u8], public_key_pem: &[u8]) -> Result<Self> {
478        let encoding_key = EncodingKey::from_ec_pem(private_key_pem).map_err(|error| {
479            Error::Jwt(JwtError::processing(
480                JwtOperation::Encode,
481                format!("failed to parse ES384 private key: {error}"),
482            ))
483        })?;
484        let decoding_key = DecodingKey::from_ec_pem(public_key_pem).map_err(|error| {
485            Error::Jwt(JwtError::processing(
486                JwtOperation::Decode,
487                format!("failed to parse ES384 public key: {error}"),
488            ))
489        })?;
490        let key_id = jwks::es384_kid_from_public_key_pem(public_key_pem)?;
491        let header = canonical_es384_header_with_kid(&key_id);
492        let validation = validation_with_es384_only();
493        let mut decoding_keys_by_kid = HashMap::new();
494        decoding_keys_by_kid.insert(key_id.clone(), decoding_key.clone());
495
496        Ok(Self {
497            encoding_key: Some(encoding_key),
498            key_id,
499            decoding_keys_by_kid,
500            fallback_decoding_key: Some(decoding_key),
501            header,
502            validation,
503        })
504    }
505
506    /// Creates verification-only ES384 JWT options from a PEM public key.
507    ///
508    /// Codecs built from these options can decode and validate tokens but will
509    /// reject encoding attempts.
510    ///
511    /// # Errors
512    ///
513    /// Returns a JWT processing error when the public key cannot be parsed.
514    pub fn for_es384_verification_only(public_key_pem: &[u8]) -> Result<Self> {
515        let decoding_key = DecodingKey::from_ec_pem(public_key_pem).map_err(|error| {
516            Error::Jwt(JwtError::processing(
517                JwtOperation::Decode,
518                format!("failed to parse ES384 public key: {error}"),
519            ))
520        })?;
521        let key_id = jwks::es384_kid_from_public_key_pem(public_key_pem)?;
522        let header = canonical_es384_header_with_kid(&key_id);
523        let validation = validation_with_es384_only();
524        let mut decoding_keys_by_kid = HashMap::new();
525        decoding_keys_by_kid.insert(key_id.clone(), decoding_key.clone());
526
527        Ok(Self {
528            encoding_key: None,
529            key_id,
530            decoding_keys_by_kid,
531            fallback_decoding_key: Some(decoding_key),
532            header,
533            validation,
534        })
535    }
536
537    /// Creates verification-only ES384 options from JWKS keys.
538    ///
539    /// This enables key selection by `kid` and rejects JWTs that do not include
540    /// a matching `kid`.
541    ///
542    /// # Errors
543    ///
544    /// Returns a JWT processing error if no valid ES384 verification key can be
545    /// constructed from the provided JWKS entries.
546    pub fn for_es384_jwks_keys(keys: &[jwks::EcP384Jwk]) -> Result<Self> {
547        if keys.is_empty() {
548            return Err(Error::Jwt(JwtError::processing(
549                JwtOperation::Validate,
550                "JWKS key set is empty",
551            )));
552        }
553
554        let mut decoding_keys_by_kid = HashMap::new();
555        for key in keys {
556            let decoding_key = key.to_decoding_key()?;
557            decoding_keys_by_kid.insert(key.kid.clone(), decoding_key);
558        }
559
560        let key_id = keys[0].kid.clone();
561        let header = canonical_es384_header_with_kid(&key_id);
562        let validation = validation_with_es384_only();
563
564        Ok(Self {
565            encoding_key: None,
566            key_id,
567            decoding_keys_by_kid,
568            fallback_decoding_key: None,
569            header,
570            validation,
571        })
572    }
573
574    /// Returns the active signing key id (`kid`).
575    pub fn key_id(&self) -> &str {
576        &self.key_id
577    }
578
579    /// Returns the number of configured verification keys.
580    pub fn verification_key_count(&self) -> usize {
581        self.decoding_keys_by_kid.len()
582    }
583
584    /// Returns whether verification accepts missing `kid` by using a fallback
585    /// decoding key.
586    pub fn allows_missing_kid_fallback(&self) -> bool {
587        self.fallback_decoding_key.is_some()
588    }
589
590    /// Returns updated options with an explicit `kid` for signing.
591    ///
592    /// This always keeps a canonical ES384 JWT header (`alg = ES384`,
593    /// `typ = JWT`, `kid = ...`).
594    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
595        let key_id = key_id.into();
596        self.key_id = key_id.clone();
597        self.header = canonical_es384_header_with_kid(&key_id);
598        self
599    }
600
601    /// Returns updated options with a replaced verification-key map.
602    ///
603    /// The first key is used as fallback only when
604    /// `allow_missing_kid_fallback` is `true`.
605    pub fn with_verification_keys(
606        mut self,
607        keys: HashMap<String, DecodingKey>,
608        allow_missing_kid_fallback: bool,
609    ) -> Self {
610        let fallback = if allow_missing_kid_fallback {
611            keys.values().next().cloned()
612        } else {
613            None
614        };
615        self.decoding_keys_by_kid = keys;
616        self.fallback_decoding_key = fallback;
617        self
618    }
619
620    /// Returns updated options with an added verification key.
621    pub fn with_added_verification_key(mut self, kid: impl Into<String>, key: DecodingKey) -> Self {
622        self.decoding_keys_by_kid.insert(kid.into(), key);
623        self
624    }
625
626    /// Returns updated options with custom validation settings.
627    pub fn with_validation(self, validation: Validation) -> Self {
628        let mut validation = validation;
629        validation.algorithms = vec![Algorithm::ES384];
630
631        Self { validation, ..self }
632    }
633}
634
635/// JWT codec backed by the `jsonwebtoken` crate.
636///
637/// This is the main codec implementation provided by the crate.
638///
639/// # Key management
640///
641/// The default constructor uses built-in ES384 development keys.
642/// This is convenient for tests or local development.
643///
644/// For persistent sessions across restarts or multiple instances, construct the
645/// codec with explicit keys via [`JsonWebToken::new_with_options`].
646#[derive(Clone)]
647pub struct JsonWebToken<P> {
648    enc_key: Option<EncodingKey>,
649    key_id: String,
650    verification_state: std::sync::Arc<RwLock<VerificationState>>,
651    header: Header,
652    validation: Validation,
653    phantom_payload: PhantomData<P>,
654}
655
656#[derive(Clone)]
657struct VerificationState {
658    dec_keys_by_kid: HashMap<String, DecodingKey>,
659    fallback_dec_key: Option<DecodingKey>,
660}
661
662impl<P> JsonWebToken<P> {
663    /// Creates a codec from explicit options.
664    ///
665    /// Use this when you need explicit signing or verification configuration.
666    pub fn new_with_options(options: JsonWebTokenOptions) -> Self {
667        let JsonWebTokenOptions {
668            encoding_key,
669            key_id,
670            decoding_keys_by_kid,
671            fallback_decoding_key,
672            header,
673            validation,
674        } = options;
675
676        Self {
677            enc_key: encoding_key,
678            key_id,
679            verification_state: std::sync::Arc::new(RwLock::new(VerificationState {
680                dec_keys_by_kid: decoding_keys_by_kid,
681                fallback_dec_key: fallback_decoding_key,
682            })),
683            header,
684            validation,
685            phantom_payload: PhantomData,
686        }
687    }
688
689    /// Returns the signing `kid` used by this codec.
690    pub fn key_id(&self) -> &str {
691        &self.key_id
692    }
693
694    /// Returns the number of currently configured verification keys.
695    pub fn verification_key_count(&self) -> usize {
696        self.verification_state
697            .read()
698            .map(|state| state.dec_keys_by_kid.len())
699            .unwrap_or(0)
700    }
701
702    /// Returns whether a verification key for the given `kid` exists.
703    pub fn has_verification_key(&self, kid: &str) -> bool {
704        self.verification_state
705            .read()
706            .map(|state| state.dec_keys_by_kid.contains_key(kid))
707            .unwrap_or(false)
708    }
709
710    /// Returns whether this codec allows verification without `kid`.
711    pub fn allows_missing_kid_fallback(&self) -> bool {
712        self.verification_state
713            .read()
714            .map(|state| state.fallback_dec_key.is_some())
715            .unwrap_or(false)
716    }
717
718    /// Atomically replaces all verification keys.
719    pub fn replace_verification_keys(
720        &self,
721        keys: HashMap<String, DecodingKey>,
722        allow_missing_kid_fallback: bool,
723    ) {
724        if let Ok(mut state) = self.verification_state.write() {
725            let fallback = if allow_missing_kid_fallback {
726                keys.values().next().cloned()
727            } else {
728                None
729            };
730            state.dec_keys_by_kid = keys;
731            state.fallback_dec_key = fallback;
732        }
733    }
734
735    /// Atomically replaces all verification keys from canonical ES384 JWK entries.
736    ///
737    /// # Errors
738    ///
739    /// Returns an error when any provided key cannot be converted.
740    pub fn replace_verification_keys_from_jwks(
741        &self,
742        keys: &[jwks::EcP384Jwk],
743        allow_missing_kid_fallback: bool,
744    ) -> Result<()> {
745        let mut decoding_keys = HashMap::new();
746        for key in keys {
747            let decoding_key = key.to_decoding_key()?;
748            decoding_keys.insert(key.kid.clone(), decoding_key);
749        }
750        self.replace_verification_keys(decoding_keys, allow_missing_kid_fallback);
751        Ok(())
752    }
753
754    fn decoding_key_for_header(&self, header: &Header) -> Result<DecodingKey> {
755        if header.alg != Algorithm::ES384 {
756            return Err(Error::Jwt(JwtError::processing(
757                JwtOperation::Validate,
758                format!(
759                    "JWT header algorithm mismatch: expected ES384 but got {:?}",
760                    header.alg
761                ),
762            )));
763        }
764
765        if header.typ.as_deref() != Some("JWT") {
766            return Err(Error::Jwt(JwtError::processing(
767                JwtOperation::Validate,
768                "JWT header `typ` must be `JWT`",
769            )));
770        }
771
772        let state = self.verification_state.read().map_err(|_| {
773            Error::Jwt(JwtError::processing(
774                JwtOperation::Validate,
775                "verification key state lock is poisoned",
776            ))
777        })?;
778
779        if let Some(kid) = header.kid.as_deref() {
780            return state.dec_keys_by_kid.get(kid).cloned().ok_or_else(|| {
781                Error::Jwt(JwtError::processing(
782                    JwtOperation::Validate,
783                    format!("JWT `kid` `{kid}` is not configured for verification"),
784                ))
785            });
786        }
787
788        state.fallback_dec_key.clone().ok_or_else(|| {
789            Error::Jwt(JwtError::processing(
790                JwtOperation::Validate,
791                "JWT header is missing `kid` and no fallback verification key is configured",
792            ))
793        })
794    }
795}
796
797impl<P> Default for JsonWebToken<P> {
798    fn default() -> Self {
799        Self::new_with_options(JsonWebTokenOptions::default())
800    }
801}
802
803impl<P> Codec for JsonWebToken<P>
804where
805    P: Serialize + DeserializeOwned + Clone,
806{
807    type Payload = P;
808
809    fn encode(&self, payload: &Self::Payload) -> Result<Vec<u8>> {
810        let Some(enc_key) = &self.enc_key else {
811            return Err(Error::Jwt(JwtError::processing(
812                JwtOperation::Encode,
813                "JWT encoding key is not configured for this codec",
814            )));
815        };
816        let token = jsonwebtoken::encode(&self.header, payload, enc_key).map_err(|error| {
817            Error::Jwt(JwtError::processing(
818                JwtOperation::Encode,
819                format!("JWT encoding failed: {error}"),
820            ))
821        })?;
822
823        Ok(token.into_bytes())
824    }
825
826    fn decode(&self, encoded_value: &[u8]) -> Result<Self::Payload> {
827        let header = decode_header(std::str::from_utf8(encoded_value).map_err(|error| {
828            Error::Jwt(JwtError::processing(
829                JwtOperation::Decode,
830                format!("JWT bytes are not valid UTF-8: {error}"),
831            ))
832        })?)
833        .map_err(|error| {
834            Error::Jwt(JwtError::processing_with_preview(
835                JwtOperation::Decode,
836                format!("JWT header decoding failed: {error}"),
837                Some(format!("token_len={}", encoded_value.len())),
838            ))
839        })?;
840
841        let decoding_key = self.decoding_key_for_header(&header)?;
842
843        let claims =
844            jsonwebtoken::decode::<Self::Payload>(encoded_value, &decoding_key, &self.validation)
845                .map_err(|error| {
846                // Do not include token bytes in the error to avoid leaking token
847                // material into logs or error messages if log levels are
848                // misconfigured.  Report only the byte length for diagnostics.
849                Error::Jwt(JwtError::processing_with_preview(
850                    JwtOperation::Decode,
851                    format!("JWT decoding failed: {error}"),
852                    Some(format!("token_len={}", encoded_value.len())),
853                ))
854            })?;
855
856        Ok(claims.claims)
857    }
858}