jwt_simple/
claims.rs

1use std::collections::HashSet;
2use std::convert::TryInto;
3
4use coarsetime::{Clock, Duration, UnixTimeStamp};
5use ct_codecs::{Base64UrlSafeNoPadding, Encoder, Hex};
6use rand::RngCore;
7use serde::{de::DeserializeOwned, Deserialize, Serialize};
8
9use crate::common::VerificationOptions;
10use crate::error::*;
11use crate::serde_additions;
12
13/// Default time tolerance value in seconds (15 minutes) for token verification.
14///
15/// This value is used to account for clock skew between systems. When verifying token
16/// expiration and validity period, this tolerance is applied to allow for slight
17/// differences in system clocks.
18///
19/// The default value is set to 15 minutes (900 seconds), which is a common practice
20/// for JWT implementations. This means:
21///
22/// - Tokens that expired less than 15 minutes ago are still considered valid
23/// - Tokens that will become valid within the next 15 minutes are already considered valid
24///
25/// You can override this value by setting the `time_tolerance` field in the `VerificationOptions`.
26pub const DEFAULT_TIME_TOLERANCE_SECS: u64 = 900;
27
28/// Empty struct representing that no application-defined claims are necessary.
29///
30/// Use this as the generic type parameter for `JWTClaims<T>` when you only need
31/// standard JWT claims and no custom application-specific data.
32///
33/// # Example
34///
35/// ```
36/// use jwt_simple::prelude::*;
37///
38/// // Create claims with no custom data
39/// let claims = Claims::create(Duration::from_hours(2));
40///
41/// // When verifying, specify NoCustomClaims as the type parameter
42/// # let key = HS256Key::generate();
43/// # let token_str = key.authenticate(claims).unwrap();
44/// let verified_claims = key.verify_token::<NoCustomClaims>(&token_str, None).unwrap();
45/// ```
46#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)]
47pub struct NoCustomClaims {}
48
49/// Representation of the JWT audience claim, which can be either a single string or a set of strings.
50///
51/// The JWT specification allows the `aud` (audience) claim to be represented either as a single
52/// string value or an array of strings. This enum provides a unified way to handle both formats.
53///
54/// # Variants
55///
56/// * `AsSet(HashSet<String>)` - Represents multiple audience values as a set of strings
57/// * `AsString(String)` - Represents a single audience value as a string
58///
59/// # Example
60///
61/// ```
62/// use jwt_simple::prelude::*;
63/// use std::collections::HashSet;
64///
65/// // Using a single audience
66/// let claims = Claims::create(Duration::from_hours(2))
67///     .with_audience("https://api.example.com");
68///
69/// // Using multiple audiences
70/// let mut audiences = HashSet::new();
71/// audiences.insert("https://api.example.com".to_string());
72/// audiences.insert("https://admin.example.com".to_string());
73/// let claims = Claims::create(Duration::from_hours(2))
74///     .with_audiences(audiences);
75/// ```
76#[derive(Debug, Clone, Eq, PartialEq)]
77pub enum Audiences {
78    /// Multiple audience values stored as a set of strings
79    AsSet(HashSet<String>),
80    /// Single audience value stored as a string
81    AsString(String),
82}
83
84impl Audiences {
85    /// Returns `true` if the audiences are represented as a set.
86    ///
87    /// This method checks whether the audience value is stored as a set of strings
88    /// rather than a single string value.
89    ///
90    /// # Example
91    ///
92    /// ```
93    /// use jwt_simple::prelude::*;
94    /// use std::collections::HashSet;
95    ///
96    /// let mut audiences = HashSet::new();
97    /// audiences.insert("audience1".to_string());
98    /// audiences.insert("audience2".to_string());
99    ///
100    /// let audience_set = Audiences::AsSet(audiences);
101    /// assert!(audience_set.is_set());
102    /// assert!(!audience_set.is_string());
103    /// ```
104    pub fn is_set(&self) -> bool {
105        matches!(self, Audiences::AsSet(_))
106    }
107
108    /// Returns `true` if the audiences are represented as a single string.
109    ///
110    /// This method checks whether the audience value is stored as a single string
111    /// rather than a set of strings.
112    ///
113    /// # Example
114    ///
115    /// ```
116    /// use jwt_simple::prelude::*;
117    ///
118    /// let audience_string = Audiences::AsString("audience1".to_string());
119    /// assert!(audience_string.is_string());
120    /// assert!(!audience_string.is_set());
121    /// ```
122    pub fn is_string(&self) -> bool {
123        matches!(self, Audiences::AsString(_))
124    }
125
126    /// Returns `true` if the audiences include any of the `allowed_audiences` entries.
127    ///
128    /// This method is used for audience verification during token validation to check
129    /// if any of the allowed audiences matches the token's audience claim.
130    ///
131    /// - For a string audience, it checks if the string exists in the `allowed_audiences` set
132    /// - For a set of audiences, it checks if there's any overlap with the `allowed_audiences` set
133    ///
134    /// # Arguments
135    ///
136    /// * `allowed_audiences` - A set of allowed audience values to check against
137    ///
138    /// # Example
139    ///
140    /// ```
141    /// use jwt_simple::prelude::*;
142    /// use std::collections::HashSet;
143    ///
144    /// let mut allowed = HashSet::new();
145    /// allowed.insert("audience1".to_string());
146    /// allowed.insert("audience2".to_string());
147    ///
148    /// // String audience
149    /// let audience_string = Audiences::AsString("audience1".to_string());
150    /// assert!(audience_string.contains(&allowed));
151    ///
152    /// // Set of audiences with overlap
153    /// let mut audiences = HashSet::new();
154    /// audiences.insert("audience2".to_string());
155    /// audiences.insert("audience3".to_string());
156    /// let audience_set = Audiences::AsSet(audiences);
157    /// assert!(audience_set.contains(&allowed));
158    /// ```
159    pub fn contains(&self, allowed_audiences: &HashSet<String>) -> bool {
160        match self {
161            Audiences::AsString(audience) => allowed_audiences.contains(audience),
162            Audiences::AsSet(audiences) => {
163                audiences.intersection(allowed_audiences).next().is_some()
164            }
165        }
166    }
167
168    /// Converts the audiences to a set of strings.
169    ///
170    /// This method consumes the `Audiences` enum and returns a `HashSet<String>`:
171    /// - If it's already a set, returns the set directly
172    /// - If it's a string, wraps it in a singleton set (unless it's empty)
173    ///
174    /// # Example
175    ///
176    /// ```
177    /// use jwt_simple::prelude::*;
178    /// use std::collections::HashSet;
179    ///
180    /// // From a string
181    /// let audience_string = Audiences::AsString("audience1".to_string());
182    /// let set = audience_string.into_set();
183    /// assert_eq!(set.len(), 1);
184    /// assert!(set.contains("audience1"));
185    ///
186    /// // From a set
187    /// let mut original_set = HashSet::new();
188    /// original_set.insert("audience1".to_string());
189    /// original_set.insert("audience2".to_string());
190    /// let audience_set = Audiences::AsSet(original_set.clone());
191    /// let result_set = audience_set.into_set();
192    /// assert_eq!(result_set, original_set);
193    /// ```
194    pub fn into_set(self) -> HashSet<String> {
195        match self {
196            Audiences::AsSet(audiences_set) => audiences_set,
197            Audiences::AsString(audiences) => {
198                let mut audiences_set = HashSet::new();
199                if !audiences.is_empty() {
200                    audiences_set.insert(audiences);
201                }
202                audiences_set
203            }
204        }
205    }
206
207    /// Converts the audiences to a single string value.
208    ///
209    /// This method consumes the `Audiences` enum and attempts to return a single `String`:
210    /// - If it's already a string, returns the string directly
211    /// - If it's a set with 0 or 1 elements, returns the single element or an empty string
212    /// - If it's a set with more than 1 element, returns an error
213    ///
214    /// # Errors
215    ///
216    /// Returns `JWTError::TooManyAudiences` if the audiences are stored as a set
217    /// with more than one element, since it cannot be unambiguously converted to a single string.
218    ///
219    /// # Example
220    ///
221    /// ```
222    /// use jwt_simple::prelude::*;
223    /// use std::collections::HashSet;
224    ///
225    /// // From a string - succeeds
226    /// let audience_string = Audiences::AsString("audience1".to_string());
227    /// let result = audience_string.into_string().unwrap();
228    /// assert_eq!(result, "audience1");
229    ///
230    /// // From an empty set - succeeds with empty string
231    /// let audience_set = Audiences::AsSet(HashSet::new());
232    /// let result = audience_set.into_string().unwrap();
233    /// assert_eq!(result, "");
234    ///
235    /// // From a set with one element - succeeds
236    /// let mut single_set = HashSet::new();
237    /// single_set.insert("audience1".to_string());
238    /// let audience_set = Audiences::AsSet(single_set);
239    /// let result = audience_set.into_string().unwrap();
240    /// assert_eq!(result, "audience1");
241    ///
242    /// // From a set with multiple elements - fails
243    /// let mut multi_set = HashSet::new();
244    /// multi_set.insert("audience1".to_string());
245    /// multi_set.insert("audience2".to_string());
246    /// let audience_set = Audiences::AsSet(multi_set);
247    /// assert!(audience_set.into_string().is_err());
248    /// ```
249    pub fn into_string(self) -> Result<String, Error> {
250        match self {
251            Audiences::AsString(audiences_str) => Ok(audiences_str),
252            Audiences::AsSet(audiences) => {
253                if audiences.len() > 1 {
254                    bail!(JWTError::TooManyAudiences);
255                }
256                Ok(audiences
257                    .iter()
258                    .next()
259                    .map(|x| x.to_string())
260                    .unwrap_or_default())
261            }
262        }
263    }
264}
265
266/// Implementation of `TryInto<String>` for `Audiences`, allowing conversion to a string
267/// using the standard `try_into()` method.
268///
269/// This delegates to the `into_string()` method, which will return an error if there
270/// are multiple audience values that cannot be unambiguously converted to a single string.
271impl TryInto<String> for Audiences {
272    type Error = Error;
273
274    fn try_into(self) -> Result<String, Error> {
275        self.into_string()
276    }
277}
278
279/// Implementation of `From<Audiences>` for `HashSet<String>`, allowing conversion to a set
280/// using the standard `into()` method.
281///
282/// This delegates to the `into_set()` method, which always succeeds.
283impl From<Audiences> for HashSet<String> {
284    fn from(audiences: Audiences) -> HashSet<String> {
285        audiences.into_set()
286    }
287}
288
289/// Convenient conversion from any string-like type to `Audiences`.
290///
291/// This converts any type that implements `ToString` into an `Audiences::AsString` variant,
292/// making it simpler to create single-audience tokens.
293///
294/// # Example
295///
296/// ```
297/// use jwt_simple::prelude::*;
298///
299/// // String conversion
300/// let audiences: Audiences = "https://api.example.com".into();
301/// assert!(audiences.is_string());
302///
303/// // &str conversion
304/// let audiences: Audiences = "https://api.example.com".into();
305/// assert!(audiences.is_string());
306/// ```
307impl<T: ToString> From<T> for Audiences {
308    fn from(audience: T) -> Self {
309        Audiences::AsString(audience.to_string())
310    }
311}
312
313/// A set of JWT claims that can include both standard JWT claims and custom application-specific data.
314///
315/// This struct represents the payload of a JWT token, containing standard registered claims
316/// defined in the JWT specification (RFC 7519) as well as optional custom claims.
317///
318/// The `CustomClaims` generic parameter allows for including application-specific data:
319/// - Use `NoCustomClaims` if you only need the standard JWT claims
320/// - Use your own type that implements `Serialize` and `Deserialize` for custom claims
321///
322/// # Standard Claims
323///
324/// - `iss` (Issuer): Identifies the principal that issued the JWT
325/// - `sub` (Subject): Identifies the principal that is the subject of the JWT
326/// - `aud` (Audience): Identifies the recipients the JWT is intended for
327/// - `exp` (Expiration Time): Identifies the time after which the JWT expires
328/// - `nbf` (Not Before): Identifies the time before which the JWT must not be accepted
329/// - `iat` (Issued At): Identifies the time at which the JWT was issued
330/// - `jti` (JWT ID): Provides a unique identifier for the JWT
331///
332/// Plus additional non-standard but commonly used claims:
333/// - `kid` (Key ID): Identifier for the key used to sign the token
334/// - `nonce`: Random value that can be used to prevent replay attacks
335///
336/// # Example
337///
338/// ```
339/// use jwt_simple::prelude::*;
340/// use serde::{Serialize, Deserialize};
341///
342/// // Using only standard claims
343/// let std_claims = Claims::create(Duration::from_hours(1))
344///     .with_issuer("auth.example.com")
345///     .with_subject("user123");
346///
347/// // Using custom claims
348/// #[derive(Serialize, Deserialize)]
349/// struct UserClaims {
350///     user_id: u64,
351///     is_admin: bool,
352/// }
353///
354/// let custom_claims = Claims::with_custom_claims(
355///     UserClaims { user_id: 42, is_admin: false },
356///     Duration::from_hours(1)
357/// ).with_issuer("auth.example.com");
358/// ```
359#[derive(Clone, Serialize, Deserialize)]
360pub struct JWTClaims<CustomClaims> {
361    /// The "Issued At" (`iat`) claim - identifies the time at which the JWT was issued.
362    ///
363    /// This claim can be used to determine the age of the token. It is represented as
364    /// the number of seconds from 1970-01-01T00:00:00Z UTC (the UNIX epoch).
365    ///
366    /// This field is automatically set when using `Claims::create()` or
367    /// `Claims::with_custom_claims()` to the current time.
368    #[serde(
369        rename = "iat",
370        default,
371        skip_serializing_if = "Option::is_none",
372        with = "self::serde_additions::unix_timestamp"
373    )]
374    pub issued_at: Option<UnixTimeStamp>,
375
376    /// The "Expiration Time" (`exp`) claim - identifies the expiration time of the token.
377    ///
378    /// This claim specifies the time after which the JWT must not be accepted for processing.
379    /// It is represented as the number of seconds from 1970-01-01T00:00:00Z UTC (the UNIX epoch).
380    ///
381    /// This field is automatically set when using `Claims::create()` or
382    /// `Claims::with_custom_claims()` to the current time plus the duration passed
383    /// as the `valid_for` parameter.
384    #[serde(
385        rename = "exp",
386        default,
387        skip_serializing_if = "Option::is_none",
388        with = "self::serde_additions::unix_timestamp"
389    )]
390    pub expires_at: Option<UnixTimeStamp>,
391
392    /// The "Not Before" (`nbf`) claim - identifies the time before which the JWT must not be accepted.
393    ///
394    /// This claim specifies the time before which the JWT must not be accepted for processing.
395    /// It is represented as the number of seconds from 1970-01-01T00:00:00Z UTC (the UNIX epoch).
396    ///
397    /// This field is automatically set when using `Claims::create()` or
398    /// `Claims::with_custom_claims()` to the current time, meaning the token is valid immediately.
399    /// It can be modified using the `invalid_before()` method.
400    #[serde(
401        rename = "nbf",
402        default,
403        skip_serializing_if = "Option::is_none",
404        with = "self::serde_additions::unix_timestamp"
405    )]
406    pub invalid_before: Option<UnixTimeStamp>,
407
408    /// The "Issuer" (`iss`) claim - identifies the principal that issued the JWT.
409    ///
410    /// This claim is a case-sensitive string and is typically a URI or an identifier for the
411    /// issuing system. It can be used to validate tokens from specific trusted issuers.
412    ///
413    /// This field is optional and can be set using the `with_issuer()` method.
414    #[serde(rename = "iss", default, skip_serializing_if = "Option::is_none")]
415    pub issuer: Option<String>,
416
417    /// The "Subject" (`sub`) claim - identifies the principal that is the subject of the JWT.
418    ///
419    /// This claim is a case-sensitive string and typically contains an identifier for the user
420    /// or entity on behalf of which the token was issued.
421    ///
422    /// This field is optional and can be set using the `with_subject()` method.
423    #[serde(rename = "sub", default, skip_serializing_if = "Option::is_none")]
424    pub subject: Option<String>,
425
426    /// The "Audience" (`aud`) claim - identifies the recipients that the JWT is intended for.
427    ///
428    /// This claim can be either a string value or an array of strings, each of which
429    /// typically identifies an intended recipient. Recipients must verify that they are
430    /// among the intended audience values.
431    ///
432    /// This field is optional and can be set using the `with_audience()` or `with_audiences()` methods.
433    #[serde(
434        rename = "aud",
435        default,
436        skip_serializing_if = "Option::is_none",
437        with = "self::serde_additions::audiences"
438    )]
439    pub audiences: Option<Audiences>,
440
441    /// The "JWT ID" (`jti`) claim - provides a unique identifier for the JWT.
442    ///
443    /// This claim creates a unique identifier for the token, which can be used to
444    /// prevent the JWT from being replayed (i.e., using the same token multiple times).
445    ///
446    /// While traditionally used for preventing replay attacks by storing all issued IDs,
447    /// this is challenging to scale. A more practical approach is to use timestamps.
448    ///
449    /// This field supports binary data through the custom Debug implementation that will
450    /// display non-UTF8 data as hex-encoded strings.
451    ///
452    /// This field is optional and can be set using the `with_jwt_id()` method.
453    #[serde(rename = "jti", default, skip_serializing_if = "Option::is_none")]
454    pub jwt_id: Option<String>,
455
456    /// The "Nonce" claim - provides a random value to prevent replay attacks.
457    ///
458    /// A nonce is a random value generated for use exactly once, which can be used to
459    /// prevent replay attacks. When a new JWT is issued, the nonce can be stored
460    /// temporarily and then checked when validating subsequent tokens.
461    ///
462    /// This field supports binary data through the custom Debug implementation that will
463    /// display non-UTF8 data as hex-encoded strings.
464    ///
465    /// This field is optional and can be set using the `with_nonce()` or `create_nonce()` methods.
466    #[serde(rename = "nonce", default, skip_serializing_if = "Option::is_none")]
467    pub nonce: Option<String>,
468
469    /// Custom application-defined claims.
470    ///
471    /// This field allows for including custom, application-specific claims in the JWT.
472    /// It must be a type that implements `Serialize` and `Deserialize`.
473    ///
474    /// Use `NoCustomClaims` if you don't need any custom claims, or your own type
475    /// to include custom data.
476    #[serde(flatten)]
477    pub custom: CustomClaims,
478}
479
480/// Custom Debug implementation for JWTClaims to handle binary data fields.
481///
482/// This implementation ensures that the `jwt_id` and `nonce` fields are displayed correctly,
483/// even if they contain non-UTF8 data:
484/// - For valid UTF-8 strings, displays them normally as strings
485/// - For strings containing invalid UTF-8 sequences, displays them as hex-encoded values
486///
487/// This is necessary because JWT tokens can contain binary data in these fields when used
488/// with CBOR Web Tokens (CWT) or when binary data is base64-encoded into JWT claims.
489impl<CustomClaims: std::fmt::Debug> std::fmt::Debug for JWTClaims<CustomClaims> {
490    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491        // Helper function to format potentially non-UTF8 strings
492        let format_binary_string = |s: &[u8]| -> String {
493            if std::str::from_utf8(s).is_ok() {
494                // If the string contains only valid UTF-8, display it as a normal string
495                format!("Some(\"{}\")", String::from_utf8_lossy(s))
496            } else {
497                // If the string contains invalid UTF-8, display it as hex
498                let hex_encoded = Hex::encode_to_string(s).unwrap_or_default();
499                format!("Some(hex: \"{}\")", hex_encoded)
500            }
501        };
502
503        // Format jwt_id, handling binary data
504        let jwt_id_display = match &self.jwt_id {
505            Some(id) => format_binary_string(id.as_bytes()),
506            None => "None".to_string(),
507        };
508
509        // Format nonce, handling binary data
510        let nonce_display = match &self.nonce {
511            Some(nonce) => format_binary_string(nonce.as_bytes()),
512            None => "None".to_string(),
513        };
514
515        // Build debug representation with properly formatted fields
516        f.debug_struct("JWTClaims")
517            .field("issued_at", &self.issued_at)
518            .field("expires_at", &self.expires_at)
519            .field("invalid_before", &self.invalid_before)
520            .field("issuer", &self.issuer)
521            .field("subject", &self.subject)
522            .field("audiences", &self.audiences)
523            .field("jwt_id", &format_args!("{}", jwt_id_display))
524            .field("nonce", &format_args!("{}", nonce_display))
525            .field("custom", &self.custom)
526            .finish()
527    }
528}
529
530impl<CustomClaims> JWTClaims<CustomClaims> {
531    /// Create a new empty JWTClaims instance with default values
532    pub fn new() -> Self
533    where
534        CustomClaims: Default,
535    {
536        JWTClaims {
537            issued_at: None,
538            expires_at: None,
539            invalid_before: None,
540            audiences: None,
541            issuer: None,
542            subject: None,
543            jwt_id: None,
544            nonce: None,
545            custom: CustomClaims::default(),
546        }
547    }
548
549    /// Validates the claims against the provided verification options.
550    ///
551    /// This method performs a thorough validation of all standard JWT claims according to
552    /// the JWT specification (RFC 7519) and the provided options. It checks:
553    ///
554    /// - Time-based claims (`exp`, `nbf`, `iat`) against the current time, with configurable tolerance
555    /// - Issuer claim (`iss`) against allowed issuers
556    /// - Subject claim (`sub`) against required subject
557    /// - Audience claim (`aud`) against allowed audiences
558    /// - JWT ID claim (`jti`) against replay protection
559    /// - Nonce claim against required nonce
560    /// - Key ID claim (`kid`) against required key ID
561    ///
562    /// # Arguments
563    ///
564    /// * `options` - The verification options to use for validating the claims
565    ///
566    /// # Returns
567    ///
568    /// * `Ok(())` if all claims are valid according to the options
569    /// * `Err(Error)` if any validation fails, with a descriptive error message
570    ///
571    /// # Example
572    ///
573    /// ```
574    /// use jwt_simple::prelude::*;
575    /// use std::collections::HashSet;
576    ///
577    /// // Create verification options
578    /// let mut options = VerificationOptions::default();
579    ///
580    /// // Configure time tolerance
581    /// options.time_tolerance = Some(Duration::from_mins(15));
582    ///
583    /// // Require specific issuer
584    /// let mut allowed_issuers = HashSet::new();
585    /// allowed_issuers.insert("auth.example.com".to_string());
586    /// options.allowed_issuers = Some(allowed_issuers);
587    ///
588    /// // Verify the claims using these options
589    /// # let key = HS256Key::generate();
590    /// # let claims = Claims::create(Duration::from_hours(1)).with_issuer("auth.example.com");
591    /// # let token_str = key.authenticate(claims).unwrap();
592    /// let verified_claims = key.verify_token::<NoCustomClaims>(&token_str, Some(options)).unwrap();
593    /// ```
594    pub(crate) fn validate(&self, options: &VerificationOptions) -> Result<(), Error> {
595        let now = options
596            .artificial_time
597            .unwrap_or_else(Clock::now_since_epoch);
598        let time_tolerance = options.time_tolerance.unwrap_or_default();
599
600        if let Some(reject_before) = options.reject_before {
601            ensure!(now >= reject_before, JWTError::OldTokenReused);
602        }
603        if let Some(time_issued) = self.issued_at {
604            ensure!(time_issued <= now + time_tolerance, JWTError::ClockDrift);
605            if let Some(max_validity) = options.max_validity {
606                ensure!(
607                    now <= time_issued || now - time_issued <= max_validity,
608                    JWTError::TokenIsTooOld
609                );
610            }
611        }
612        if !options.accept_future {
613            if let Some(invalid_before) = self.invalid_before {
614                ensure!(
615                    now + time_tolerance >= invalid_before,
616                    JWTError::TokenNotValidYet
617                );
618            }
619        }
620        if let Some(expires_at) = self.expires_at {
621            ensure!(
622                now >= time_tolerance && now - time_tolerance <= expires_at,
623                JWTError::TokenHasExpired
624            );
625        }
626        if let Some(allowed_issuers) = &options.allowed_issuers {
627            if let Some(issuer) = &self.issuer {
628                ensure!(
629                    allowed_issuers.contains(issuer),
630                    JWTError::RequiredIssuerMismatch
631                );
632            } else {
633                bail!(JWTError::RequiredIssuerMissing);
634            }
635        }
636        if let Some(required_subject) = &options.required_subject {
637            if let Some(subject) = &self.subject {
638                ensure!(
639                    subject == required_subject,
640                    JWTError::RequiredSubjectMismatch
641                );
642            } else {
643                bail!(JWTError::RequiredSubjectMissing);
644            }
645        }
646        if let Some(required_nonce) = &options.required_nonce {
647            if let Some(nonce) = &self.nonce {
648                ensure!(nonce == required_nonce, JWTError::RequiredNonceMismatch);
649            } else {
650                bail!(JWTError::RequiredNonceMissing);
651            }
652        }
653        if let Some(allowed_audiences) = &options.allowed_audiences {
654            if let Some(audiences) = &self.audiences {
655                ensure!(
656                    audiences.contains(allowed_audiences),
657                    JWTError::RequiredAudienceMismatch
658                );
659            } else {
660                bail!(JWTError::RequiredAudienceMissing);
661            }
662        }
663        Ok(())
664    }
665
666    /// Sets the token as not being valid until the specified timestamp.
667    ///
668    /// This sets the `nbf` (Not Before) claim, which specifies the time before which the token
669    /// must not be accepted for processing.
670    ///
671    /// # Arguments
672    ///
673    /// * `unix_timestamp` - The UNIX timestamp (in seconds) before which the token should be rejected
674    ///
675    /// # Returns
676    ///
677    /// * The modified claims object for method chaining
678    ///
679    /// # Example
680    ///
681    /// ```
682    /// use jwt_simple::prelude::*;
683    ///
684    /// // Token will not be valid until 1 hour from now
685    /// let future_time = Clock::now_since_epoch() + Duration::from_hours(1);
686    /// let claims = Claims::create(Duration::from_hours(2))
687    ///     .invalid_before(future_time);
688    /// ```
689    pub fn invalid_before(mut self, unix_timestamp: UnixTimeStamp) -> Self {
690        self.invalid_before = Some(unix_timestamp);
691        self
692    }
693
694    /// Sets the issuer claim (`iss`) for the token.
695    ///
696    /// The issuer claim identifies the principal that issued the JWT.
697    /// This can be used during token verification to ensure the token comes
698    /// from a trusted issuer.
699    ///
700    /// # Arguments
701    ///
702    /// * `issuer` - Any type that can be converted to a string, identifying the issuer
703    ///
704    /// # Returns
705    ///
706    /// * The modified claims object for method chaining
707    ///
708    /// # Example
709    ///
710    /// ```
711    /// use jwt_simple::prelude::*;
712    ///
713    /// let claims = Claims::create(Duration::from_hours(2))
714    ///     .with_issuer("auth.example.com");
715    /// ```
716    pub fn with_issuer(mut self, issuer: impl ToString) -> Self {
717        self.issuer = Some(issuer.to_string());
718        self
719    }
720
721    /// Sets the subject claim (`sub`) for the token.
722    ///
723    /// The subject claim identifies the principal that is the subject of the JWT.
724    /// This is typically the user ID or another identifier for the token's subject.
725    ///
726    /// # Arguments
727    ///
728    /// * `subject` - Any type that can be converted to a string, identifying the subject
729    ///
730    /// # Returns
731    ///
732    /// * The modified claims object for method chaining
733    ///
734    /// # Example
735    ///
736    /// ```
737    /// use jwt_simple::prelude::*;
738    ///
739    /// let claims = Claims::create(Duration::from_hours(2))
740    ///     .with_subject("user123@example.com");
741    /// ```
742    pub fn with_subject(mut self, subject: impl ToString) -> Self {
743        self.subject = Some(subject.to_string());
744        self
745    }
746
747    /// Sets multiple audience values (`aud`) for the token as a set.
748    ///
749    /// The audience claim identifies the recipients that the JWT is intended for.
750    /// This method allows specifying multiple audience values as a set.
751    ///
752    /// # Arguments
753    ///
754    /// * `audiences` - A HashSet of audience values that can be converted to strings
755    ///
756    /// # Returns
757    ///
758    /// * The modified claims object for method chaining
759    ///
760    /// # Example
761    ///
762    /// ```
763    /// use jwt_simple::prelude::*;
764    /// use std::collections::HashSet;
765    ///
766    /// let mut audiences = HashSet::new();
767    /// audiences.insert("https://api.example.com");
768    /// audiences.insert("https://admin.example.com");
769    ///
770    /// let claims = Claims::create(Duration::from_hours(2))
771    ///     .with_audiences(audiences);
772    /// ```
773    pub fn with_audiences(mut self, audiences: HashSet<impl ToString>) -> Self {
774        self.audiences = Some(Audiences::AsSet(
775            audiences.iter().map(|x| x.to_string()).collect(),
776        ));
777        self
778    }
779
780    /// Sets a single audience value (`aud`) for the token as a string.
781    ///
782    /// The audience claim identifies the recipient that the JWT is intended for.
783    /// This method is convenient when you only need to specify a single audience.
784    ///
785    /// # Arguments
786    ///
787    /// * `audience` - Any type that can be converted to a string, identifying the audience
788    ///
789    /// # Returns
790    ///
791    /// * The modified claims object for method chaining
792    ///
793    /// # Example
794    ///
795    /// ```
796    /// use jwt_simple::prelude::*;
797    ///
798    /// let claims = Claims::create(Duration::from_hours(2))
799    ///     .with_audience("https://api.example.com");
800    /// ```
801    pub fn with_audience(mut self, audience: impl ToString) -> Self {
802        self.audiences = Some(Audiences::AsString(audience.to_string()));
803        self
804    }
805
806    /// Sets the JWT ID claim (`jti`) for the token.
807    ///
808    /// The JWT ID claim provides a unique identifier for the JWT, which can be used
809    /// to prevent the token from being replayed. This is useful when a one-time token
810    /// is needed.
811    ///
812    /// # Arguments
813    ///
814    /// * `jwt_id` - Any type that can be converted to a string, providing a unique ID
815    ///
816    /// # Returns
817    ///
818    /// * The modified claims object for method chaining
819    ///
820    /// # Example
821    ///
822    /// ```
823    /// use jwt_simple::prelude::*;
824    ///
825    /// let claims = Claims::create(Duration::from_hours(2))
826    ///     .with_jwt_id("token-123456");
827    /// ```
828    pub fn with_jwt_id(mut self, jwt_id: impl ToString) -> Self {
829        self.jwt_id = Some(jwt_id.to_string());
830        self
831    }
832
833    /// Sets the nonce claim for the token.
834    ///
835    /// A nonce is a random value that can be used to prevent replay attacks.
836    /// When a new JWT is created, a nonce can be included and stored. When a JWT
837    /// is received for verification, the previously stored nonce can be validated.
838    ///
839    /// # Arguments
840    ///
841    /// * `nonce` - Any type that can be converted to a string, representing the nonce
842    ///
843    /// # Returns
844    ///
845    /// * The modified claims object for method chaining
846    ///
847    /// # Example
848    ///
849    /// ```
850    /// use jwt_simple::prelude::*;
851    ///
852    /// let claims = Claims::create(Duration::from_hours(2))
853    ///     .with_nonce("random-nonce-value");
854    /// ```
855    pub fn with_nonce(mut self, nonce: impl ToString) -> Self {
856        self.nonce = Some(nonce.to_string());
857        self
858    }
859
860    /// Creates a cryptographically secure random nonce, attaches it to the claims, and returns it.
861    ///
862    /// This method generates a 24-byte random nonce, encodes it using Base64UrlSafeNoPadding,
863    /// attaches it to the claims, and returns the generated nonce. This is useful for creating
864    /// tokens with built-in protection against replay attacks.
865    ///
866    /// # Returns
867    ///
868    /// * A string containing the Base64UrlSafeNoPadding-encoded nonce
869    ///
870    /// # Example
871    ///
872    /// ```
873    /// use jwt_simple::prelude::*;
874    ///
875    /// let mut claims = Claims::create(Duration::from_hours(2));
876    /// let nonce = claims.create_nonce();
877    /// // Store nonce for later verification
878    /// ```
879    pub fn create_nonce(&mut self) -> String {
880        let mut raw_nonce = [0u8; 24];
881        let mut rng = rand::thread_rng();
882        rng.fill_bytes(&mut raw_nonce);
883        let nonce = Base64UrlSafeNoPadding::encode_to_string(raw_nonce).unwrap();
884        self.nonce = Some(nonce);
885        self.nonce.as_deref().unwrap().to_string()
886    }
887}
888
889/// Factory for creating JWT claim sets with standard and custom claims.
890///
891/// This struct provides static methods for creating JWT claims with or without
892/// custom application-specific data.
893pub struct Claims;
894
895impl Claims {
896    /// Creates a new set of claims with standard JWT fields but no custom data.
897    ///
898    /// This method initializes a new claims object with:
899    /// - `iat` (Issued At) set to the current time
900    /// - `exp` (Expiration Time) set to the current time plus the specified duration
901    /// - `nbf` (Not Before) set to the current time
902    /// - All other standard claims initialized to None
903    /// - No custom claims (using `NoCustomClaims`)
904    ///
905    /// # Arguments
906    ///
907    /// * `valid_for` - The duration for which the token should be valid
908    ///
909    /// # Returns
910    ///
911    /// * A new `JWTClaims<NoCustomClaims>` object that can be further customized with the builder pattern
912    ///
913    /// # Example
914    ///
915    /// ```
916    /// use jwt_simple::prelude::*;
917    ///
918    /// // Create a token valid for 1 hour with standard fields
919    /// let claims = Claims::create(Duration::from_hours(1))
920    ///     .with_issuer("auth.example.com")
921    ///     .with_subject("user123");
922    ///
923    /// // Token can be created with any supported algorithm
924    /// let key = HS256Key::generate();
925    /// let token = key.authenticate(claims).unwrap();
926    /// ```
927    pub fn create(valid_for: Duration) -> JWTClaims<NoCustomClaims> {
928        let now = Clock::now_since_epoch();
929        JWTClaims {
930            issued_at: Some(now),
931            expires_at: Some(now + valid_for),
932            invalid_before: Some(now),
933            audiences: None,
934            issuer: None,
935            jwt_id: None,
936            subject: None,
937            nonce: None,
938            custom: NoCustomClaims {},
939        }
940    }
941
942    /// Creates a new set of claims with both standard JWT fields and custom application data.
943    ///
944    /// This method initializes a new claims object with:
945    /// - `iat` (Issued At) set to the current time
946    /// - `exp` (Expiration Time) set to the current time plus the specified duration
947    /// - `nbf` (Not Before) set to the current time
948    /// - All other standard claims initialized to None
949    /// - The provided custom claims
950    ///
951    /// # Type Parameters
952    ///
953    /// * `CustomClaims` - A type that implements `Serialize` and `DeserializeOwned` for custom application data
954    ///
955    /// # Arguments
956    ///
957    /// * `custom_claims` - The application-specific data to include in the token
958    /// * `valid_for` - The duration for which the token should be valid
959    ///
960    /// # Returns
961    ///
962    /// * A new `JWTClaims<CustomClaims>` object that can be further customized with the builder pattern
963    ///
964    /// # Example
965    ///
966    /// ```
967    /// use jwt_simple::prelude::*;
968    /// use serde::{Serialize, Deserialize};
969    ///
970    /// #[derive(Serialize, Deserialize)]
971    /// struct UserClaims {
972    ///     user_id: u64,
973    ///     roles: Vec<String>,
974    ///     email: String,
975    /// }
976    ///
977    /// // Create custom claims
978    /// let user_data = UserClaims {
979    ///     user_id: 1234,
980    ///     roles: vec!["user".to_string(), "admin".to_string()],
981    ///     email: "user@example.com".to_string(),
982    /// };
983    ///
984    /// // Create a token valid for 1 hour with custom data
985    /// let claims = Claims::with_custom_claims(user_data, Duration::from_hours(1))
986    ///     .with_issuer("auth.example.com");
987    ///
988    /// // Token can be created with any supported algorithm
989    /// let key_pair = ES256KeyPair::generate();
990    /// let token = key_pair.sign(claims).unwrap();
991    /// ```
992    pub fn with_custom_claims<CustomClaims: Serialize + DeserializeOwned>(
993        custom_claims: CustomClaims,
994        valid_for: Duration,
995    ) -> JWTClaims<CustomClaims> {
996        let now = Clock::now_since_epoch();
997        JWTClaims {
998            issued_at: Some(now),
999            expires_at: Some(now + valid_for),
1000            invalid_before: Some(now),
1001            audiences: None,
1002            issuer: None,
1003            jwt_id: None,
1004            subject: None,
1005            nonce: None,
1006            custom: custom_claims,
1007        }
1008    }
1009}
1010
1011impl Default for JWTClaims<NoCustomClaims> {
1012    fn default() -> Self {
1013        JWTClaims {
1014            issued_at: None,
1015            expires_at: None,
1016            invalid_before: None,
1017            audiences: None,
1018            issuer: None,
1019            jwt_id: None,
1020            subject: None,
1021            nonce: None,
1022            custom: NoCustomClaims::default(),
1023        }
1024    }
1025}
1026
1027#[cfg(test)]
1028mod tests {
1029    use super::*;
1030
1031    #[test]
1032    fn should_set_standard_claims() {
1033        let exp = Duration::from_mins(10);
1034        let mut audiences = HashSet::new();
1035        audiences.insert("audience1".to_string());
1036        audiences.insert("audience2".to_string());
1037        let claims = Claims::create(exp)
1038            .with_audiences(audiences.clone())
1039            .with_issuer("issuer")
1040            .with_jwt_id("jwt_id")
1041            .with_nonce("nonce")
1042            .with_subject("subject");
1043
1044        assert_eq!(claims.audiences, Some(Audiences::AsSet(audiences)));
1045        assert_eq!(claims.issuer, Some("issuer".to_owned()));
1046        assert_eq!(claims.jwt_id, Some("jwt_id".to_owned()));
1047        assert_eq!(claims.nonce, Some("nonce".to_owned()));
1048        assert_eq!(claims.subject, Some("subject".to_owned()));
1049    }
1050
1051    #[test]
1052    fn parse_floating_point_unix_time() {
1053        let claims: JWTClaims<()> = serde_json::from_str(r#"{"exp":1617757825.8}"#).unwrap();
1054        assert_eq!(
1055            claims.expires_at,
1056            Some(UnixTimeStamp::from_secs(1617757825))
1057        );
1058    }
1059
1060    #[test]
1061    fn should_tolerate_clock_drift() {
1062        let exp = Duration::from_mins(1);
1063        let claims = Claims::create(exp);
1064        let mut options = VerificationOptions::default();
1065
1066        // Verifier clock is 2 minutes ahead of the token clock.
1067        // The token is valid for 1 minute, with an extra tolerance of 1 minute.
1068        // Verification should pass.
1069        let drift = Duration::from_mins(2);
1070        options.artificial_time = Some(claims.issued_at.unwrap() + drift);
1071        options.time_tolerance = Some(Duration::from_mins(1));
1072        claims.validate(&options).unwrap();
1073
1074        // Verifier clock is 2 minutes ahead of the token clock.
1075        // The token is valid for 1 minute, with an extra tolerance of 1 minute.
1076        // Verification must not pass.
1077        let drift = Duration::from_mins(3);
1078        options.artificial_time = Some(claims.issued_at.unwrap() + drift);
1079        options.time_tolerance = Some(Duration::from_mins(1));
1080        assert!(claims.validate(&options).is_err());
1081
1082        // Verifier clock is 2 minutes ahead of the token clock.
1083        // The token is valid for 30 seconds, with an extra tolerance of 1 minute.
1084        // Verification must not pass.
1085        let drift = Duration::from_secs(30);
1086        options.artificial_time = Some(claims.issued_at.unwrap() + drift);
1087        options.time_tolerance = Some(Duration::from_mins(1));
1088        claims.validate(&options).unwrap();
1089
1090        // Verifier clock is 2 minutes behind the token clock.
1091        // The token is valid for 1 minute, so it is already expired.
1092        // We have a tolerance of 1 minute.
1093        // Verification must not pass.
1094        let drift = Duration::from_mins(2);
1095        options.artificial_time = Some(claims.issued_at.unwrap() - drift);
1096        options.time_tolerance = Some(Duration::from_mins(1));
1097        assert!(claims.validate(&options).is_err());
1098
1099        // Verifier clock is 2 minutes behind the token clock.
1100        // The token is valid for 1 minute, so it is already expired.
1101        // We have a tolerance of 2 minute.
1102        // Verification should pass.
1103        let drift = Duration::from_mins(2);
1104        options.artificial_time = Some(claims.issued_at.unwrap() - drift);
1105        options.time_tolerance = Some(Duration::from_mins(2));
1106        claims.validate(&options).unwrap();
1107    }
1108
1109    #[test]
1110    fn debug_displays_jwt_id_correctly() {
1111        let exp = Duration::from_mins(10);
1112
1113        // Test valid UTF-8
1114        let claims1 = Claims::create(exp).with_jwt_id("valid-utf8-jwt-id");
1115        let debug_str1 = format!("{:?}", claims1);
1116        assert!(debug_str1.contains("jwt_id: Some(\"valid-utf8-jwt-id\")"));
1117
1118        // Create a binary JWT ID containing bytes that cannot be represented as valid UTF-8
1119        // We'll use a base64-encoded string with deliberately non-UTF8 bytes
1120        let binary_jwt_id =
1121            Base64UrlSafeNoPadding::encode_to_string(vec![0xff, 0x00, 0xfe, 0x7f]).unwrap();
1122
1123        // Create claims with the JWT ID containing binary data
1124        let claims2 = Claims::create(exp).with_jwt_id(binary_jwt_id);
1125
1126        // We need to modify the test assertion. Since we're representing the binary data
1127        // with a valid base64-encoded string (which is valid UTF-8), it will be displayed
1128        // as a regular string, not as hex. However, we still want to check that the Debug
1129        // implementation works correctly.
1130        let debug_str2 = format!("{:?}", claims2);
1131
1132        // The JWT ID will be displayed normally, so we'll just check the basic formatting
1133        assert!(debug_str2.contains("jwt_id: Some("));
1134    }
1135}