Skip to main content

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::Rng;
7use serde::{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            let issued_at = self.issued_at.ok_or(JWTError::OldTokenReused)?;
602            ensure!(issued_at >= reject_before, JWTError::OldTokenReused);
603        }
604        if !options.accept_future {
605            if let Some(time_issued) = self.issued_at {
606                ensure!(time_issued <= now + time_tolerance, JWTError::ClockDrift);
607            }
608        }
609        if let Some(max_validity) = options.max_validity {
610            let time_issued = self.issued_at.ok_or(JWTError::TokenIsTooOld)?;
611            ensure!(
612                now <= time_issued || now - time_issued <= max_validity,
613                JWTError::TokenIsTooOld
614            );
615        }
616        if !options.accept_future {
617            if let Some(invalid_before) = self.invalid_before {
618                ensure!(
619                    now + time_tolerance >= invalid_before,
620                    JWTError::TokenNotValidYet
621                );
622            }
623        }
624        if let Some(expires_at) = self.expires_at {
625            ensure!(
626                now >= time_tolerance && now - time_tolerance <= expires_at,
627                JWTError::TokenHasExpired
628            );
629        }
630        if let Some(allowed_issuers) = &options.allowed_issuers {
631            if let Some(issuer) = &self.issuer {
632                ensure!(
633                    allowed_issuers.contains(issuer),
634                    JWTError::RequiredIssuerMismatch
635                );
636            } else {
637                bail!(JWTError::RequiredIssuerMissing);
638            }
639        }
640        if let Some(required_subject) = &options.required_subject {
641            if let Some(subject) = &self.subject {
642                ensure!(
643                    subject == required_subject,
644                    JWTError::RequiredSubjectMismatch
645                );
646            } else {
647                bail!(JWTError::RequiredSubjectMissing);
648            }
649        }
650        if let Some(required_nonce) = &options.required_nonce {
651            if let Some(nonce) = &self.nonce {
652                ensure!(nonce == required_nonce, JWTError::RequiredNonceMismatch);
653            } else {
654                bail!(JWTError::RequiredNonceMissing);
655            }
656        }
657        if let Some(allowed_audiences) = &options.allowed_audiences {
658            if let Some(audiences) = &self.audiences {
659                ensure!(
660                    audiences.contains(allowed_audiences),
661                    JWTError::RequiredAudienceMismatch
662                );
663            } else {
664                bail!(JWTError::RequiredAudienceMissing);
665            }
666        }
667        Ok(())
668    }
669
670    /// Sets the token as not being valid until the specified timestamp.
671    ///
672    /// This sets the `nbf` (Not Before) claim, which specifies the time before which the token
673    /// must not be accepted for processing.
674    ///
675    /// # Arguments
676    ///
677    /// * `unix_timestamp` - The UNIX timestamp (in seconds) before which the token should be rejected
678    ///
679    /// # Returns
680    ///
681    /// * The modified claims object for method chaining
682    ///
683    /// # Example
684    ///
685    /// ```
686    /// use jwt_simple::prelude::*;
687    ///
688    /// // Token will not be valid until 1 hour from now
689    /// let future_time = Clock::now_since_epoch() + Duration::from_hours(1);
690    /// let claims = Claims::create(Duration::from_hours(2))
691    ///     .invalid_before(future_time);
692    /// ```
693    pub fn invalid_before(mut self, unix_timestamp: UnixTimeStamp) -> Self {
694        self.invalid_before = Some(unix_timestamp);
695        self
696    }
697
698    /// Sets the issuer claim (`iss`) for the token.
699    ///
700    /// The issuer claim identifies the principal that issued the JWT.
701    /// This can be used during token verification to ensure the token comes
702    /// from a trusted issuer.
703    ///
704    /// # Arguments
705    ///
706    /// * `issuer` - Any type that can be converted to a string, identifying the issuer
707    ///
708    /// # Returns
709    ///
710    /// * The modified claims object for method chaining
711    ///
712    /// # Example
713    ///
714    /// ```
715    /// use jwt_simple::prelude::*;
716    ///
717    /// let claims = Claims::create(Duration::from_hours(2))
718    ///     .with_issuer("auth.example.com");
719    /// ```
720    pub fn with_issuer(mut self, issuer: impl ToString) -> Self {
721        self.issuer = Some(issuer.to_string());
722        self
723    }
724
725    /// Sets the subject claim (`sub`) for the token.
726    ///
727    /// The subject claim identifies the principal that is the subject of the JWT.
728    /// This is typically the user ID or another identifier for the token's subject.
729    ///
730    /// # Arguments
731    ///
732    /// * `subject` - Any type that can be converted to a string, identifying the subject
733    ///
734    /// # Returns
735    ///
736    /// * The modified claims object for method chaining
737    ///
738    /// # Example
739    ///
740    /// ```
741    /// use jwt_simple::prelude::*;
742    ///
743    /// let claims = Claims::create(Duration::from_hours(2))
744    ///     .with_subject("user123@example.com");
745    /// ```
746    pub fn with_subject(mut self, subject: impl ToString) -> Self {
747        self.subject = Some(subject.to_string());
748        self
749    }
750
751    /// Sets multiple audience values (`aud`) for the token as a set.
752    ///
753    /// The audience claim identifies the recipients that the JWT is intended for.
754    /// This method allows specifying multiple audience values as a set.
755    ///
756    /// # Arguments
757    ///
758    /// * `audiences` - A HashSet of audience values that can be converted to strings
759    ///
760    /// # Returns
761    ///
762    /// * The modified claims object for method chaining
763    ///
764    /// # Example
765    ///
766    /// ```
767    /// use jwt_simple::prelude::*;
768    /// use std::collections::HashSet;
769    ///
770    /// let mut audiences = HashSet::new();
771    /// audiences.insert("https://api.example.com");
772    /// audiences.insert("https://admin.example.com");
773    ///
774    /// let claims = Claims::create(Duration::from_hours(2))
775    ///     .with_audiences(audiences);
776    /// ```
777    pub fn with_audiences(mut self, audiences: HashSet<impl ToString>) -> Self {
778        self.audiences = Some(Audiences::AsSet(
779            audiences.iter().map(|x| x.to_string()).collect(),
780        ));
781        self
782    }
783
784    /// Sets a single audience value (`aud`) for the token as a string.
785    ///
786    /// The audience claim identifies the recipient that the JWT is intended for.
787    /// This method is convenient when you only need to specify a single audience.
788    ///
789    /// # Arguments
790    ///
791    /// * `audience` - Any type that can be converted to a string, identifying the audience
792    ///
793    /// # Returns
794    ///
795    /// * The modified claims object for method chaining
796    ///
797    /// # Example
798    ///
799    /// ```
800    /// use jwt_simple::prelude::*;
801    ///
802    /// let claims = Claims::create(Duration::from_hours(2))
803    ///     .with_audience("https://api.example.com");
804    /// ```
805    pub fn with_audience(mut self, audience: impl ToString) -> Self {
806        self.audiences = Some(Audiences::AsString(audience.to_string()));
807        self
808    }
809
810    /// Sets the JWT ID claim (`jti`) for the token.
811    ///
812    /// The JWT ID claim provides a unique identifier for the JWT, which can be used
813    /// to prevent the token from being replayed. This is useful when a one-time token
814    /// is needed.
815    ///
816    /// # Arguments
817    ///
818    /// * `jwt_id` - Any type that can be converted to a string, providing a unique ID
819    ///
820    /// # Returns
821    ///
822    /// * The modified claims object for method chaining
823    ///
824    /// # Example
825    ///
826    /// ```
827    /// use jwt_simple::prelude::*;
828    ///
829    /// let claims = Claims::create(Duration::from_hours(2))
830    ///     .with_jwt_id("token-123456");
831    /// ```
832    pub fn with_jwt_id(mut self, jwt_id: impl ToString) -> Self {
833        self.jwt_id = Some(jwt_id.to_string());
834        self
835    }
836
837    /// Sets the nonce claim for the token.
838    ///
839    /// A nonce is a random value that can be used to prevent replay attacks.
840    /// When a new JWT is created, a nonce can be included and stored. When a JWT
841    /// is received for verification, the previously stored nonce can be validated.
842    ///
843    /// # Arguments
844    ///
845    /// * `nonce` - Any type that can be converted to a string, representing the nonce
846    ///
847    /// # Returns
848    ///
849    /// * The modified claims object for method chaining
850    ///
851    /// # Example
852    ///
853    /// ```
854    /// use jwt_simple::prelude::*;
855    ///
856    /// let claims = Claims::create(Duration::from_hours(2))
857    ///     .with_nonce("random-nonce-value");
858    /// ```
859    pub fn with_nonce(mut self, nonce: impl ToString) -> Self {
860        self.nonce = Some(nonce.to_string());
861        self
862    }
863
864    /// Creates a cryptographically secure random nonce, attaches it to the claims, and returns it.
865    ///
866    /// This method generates a 24-byte random nonce, encodes it using Base64UrlSafeNoPadding,
867    /// attaches it to the claims, and returns the generated nonce. This is useful for creating
868    /// tokens with built-in protection against replay attacks.
869    ///
870    /// # Returns
871    ///
872    /// * A string containing the Base64UrlSafeNoPadding-encoded nonce
873    ///
874    /// # Example
875    ///
876    /// ```
877    /// use jwt_simple::prelude::*;
878    ///
879    /// let mut claims = Claims::create(Duration::from_hours(2));
880    /// let nonce = claims.create_nonce();
881    /// // Store nonce for later verification
882    /// ```
883    pub fn create_nonce(&mut self) -> String {
884        let mut raw_nonce = [0u8; 24];
885        let mut rng = rand::rng();
886        rng.fill_bytes(&mut raw_nonce);
887        let nonce = Base64UrlSafeNoPadding::encode_to_string(raw_nonce).unwrap();
888        self.nonce = Some(nonce);
889        self.nonce.as_deref().unwrap().to_string()
890    }
891}
892
893/// Factory for creating JWT claim sets with standard and custom claims.
894///
895/// This struct provides static methods for creating JWT claims with or without
896/// custom application-specific data.
897pub struct Claims;
898
899impl Claims {
900    /// Creates a new set of claims with standard JWT fields but no custom data.
901    ///
902    /// This method initializes a new claims object with:
903    /// - `iat` (Issued At) set to the current time
904    /// - `exp` (Expiration Time) set to the current time plus the specified duration
905    /// - `nbf` (Not Before) set to the current time
906    /// - All other standard claims initialized to None
907    /// - No custom claims (using `NoCustomClaims`)
908    ///
909    /// # Arguments
910    ///
911    /// * `valid_for` - The duration for which the token should be valid
912    ///
913    /// # Returns
914    ///
915    /// * A new `JWTClaims<NoCustomClaims>` object that can be further customized with the builder pattern
916    ///
917    /// # Example
918    ///
919    /// ```
920    /// use jwt_simple::prelude::*;
921    ///
922    /// // Create a token valid for 1 hour with standard fields
923    /// let claims = Claims::create(Duration::from_hours(1))
924    ///     .with_issuer("auth.example.com")
925    ///     .with_subject("user123");
926    ///
927    /// // Token can be created with any supported algorithm
928    /// let key = HS256Key::generate();
929    /// let token = key.authenticate(claims).unwrap();
930    /// ```
931    pub fn create(valid_for: Duration) -> JWTClaims<NoCustomClaims> {
932        let now = Clock::now_since_epoch();
933        JWTClaims {
934            issued_at: Some(now),
935            expires_at: Some(now + valid_for),
936            invalid_before: Some(now),
937            audiences: None,
938            issuer: None,
939            jwt_id: None,
940            subject: None,
941            nonce: None,
942            custom: NoCustomClaims {},
943        }
944    }
945
946    /// Creates a new set of claims with both standard JWT fields and custom application data.
947    ///
948    /// This method initializes a new claims object with:
949    /// - `iat` (Issued At) set to the current time
950    /// - `exp` (Expiration Time) set to the current time plus the specified duration
951    /// - `nbf` (Not Before) set to the current time
952    /// - All other standard claims initialized to None
953    /// - The provided custom claims
954    ///
955    /// # Type Parameters
956    ///
957    /// * `CustomClaims` - A type that implements `Serialize` for custom application data
958    ///
959    /// # Arguments
960    ///
961    /// * `custom_claims` - The application-specific data to include in the token
962    /// * `valid_for` - The duration for which the token should be valid
963    ///
964    /// # Returns
965    ///
966    /// * A new `JWTClaims<CustomClaims>` object that can be further customized with the builder pattern
967    ///
968    /// # Example
969    ///
970    /// ```
971    /// use jwt_simple::prelude::*;
972    /// use serde::{Serialize, Deserialize};
973    ///
974    /// #[derive(Serialize, Deserialize)]
975    /// struct UserClaims {
976    ///     user_id: u64,
977    ///     roles: Vec<String>,
978    ///     email: String,
979    /// }
980    ///
981    /// // Create custom claims
982    /// let user_data = UserClaims {
983    ///     user_id: 1234,
984    ///     roles: vec!["user".to_string(), "admin".to_string()],
985    ///     email: "user@example.com".to_string(),
986    /// };
987    ///
988    /// // Create a token valid for 1 hour with custom data
989    /// let claims = Claims::with_custom_claims(user_data, Duration::from_hours(1))
990    ///     .with_issuer("auth.example.com");
991    ///
992    /// // Token can be created with any supported algorithm
993    /// let key_pair = ES256KeyPair::generate();
994    /// let token = key_pair.sign(claims).unwrap();
995    /// ```
996    pub fn with_custom_claims<CustomClaims: Serialize>(
997        custom_claims: CustomClaims,
998        valid_for: Duration,
999    ) -> JWTClaims<CustomClaims> {
1000        let now = Clock::now_since_epoch();
1001        JWTClaims {
1002            issued_at: Some(now),
1003            expires_at: Some(now + valid_for),
1004            invalid_before: Some(now),
1005            audiences: None,
1006            issuer: None,
1007            jwt_id: None,
1008            subject: None,
1009            nonce: None,
1010            custom: custom_claims,
1011        }
1012    }
1013}
1014
1015impl Default for JWTClaims<NoCustomClaims> {
1016    fn default() -> Self {
1017        JWTClaims {
1018            issued_at: None,
1019            expires_at: None,
1020            invalid_before: None,
1021            audiences: None,
1022            issuer: None,
1023            jwt_id: None,
1024            subject: None,
1025            nonce: None,
1026            custom: NoCustomClaims::default(),
1027        }
1028    }
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034
1035    #[test]
1036    fn should_set_standard_claims() {
1037        let exp = Duration::from_mins(10);
1038        let mut audiences = HashSet::new();
1039        audiences.insert("audience1".to_string());
1040        audiences.insert("audience2".to_string());
1041        let claims = Claims::create(exp)
1042            .with_audiences(audiences.clone())
1043            .with_issuer("issuer")
1044            .with_jwt_id("jwt_id")
1045            .with_nonce("nonce")
1046            .with_subject("subject");
1047
1048        assert_eq!(claims.audiences, Some(Audiences::AsSet(audiences)));
1049        assert_eq!(claims.issuer, Some("issuer".to_owned()));
1050        assert_eq!(claims.jwt_id, Some("jwt_id".to_owned()));
1051        assert_eq!(claims.nonce, Some("nonce".to_owned()));
1052        assert_eq!(claims.subject, Some("subject".to_owned()));
1053    }
1054
1055    #[test]
1056    fn parse_floating_point_unix_time() {
1057        let claims: JWTClaims<()> = serde_json::from_str(r#"{"exp":1617757825.8}"#).unwrap();
1058        assert_eq!(
1059            claims.expires_at,
1060            Some(UnixTimeStamp::from_secs(1617757825))
1061        );
1062    }
1063
1064    #[test]
1065    fn should_tolerate_clock_drift() {
1066        let exp = Duration::from_mins(1);
1067        let claims = Claims::create(exp);
1068        let mut options = VerificationOptions::default();
1069
1070        // Verifier clock is 2 minutes ahead of the token clock.
1071        // The token is valid for 1 minute, with an extra tolerance of 1 minute.
1072        // Verification should pass.
1073        let drift = Duration::from_mins(2);
1074        options.artificial_time = Some(claims.issued_at.unwrap() + drift);
1075        options.time_tolerance = Some(Duration::from_mins(1));
1076        claims.validate(&options).unwrap();
1077
1078        // Verifier clock is 2 minutes ahead of the token clock.
1079        // The token is valid for 1 minute, with an extra tolerance of 1 minute.
1080        // Verification must not pass.
1081        let drift = Duration::from_mins(3);
1082        options.artificial_time = Some(claims.issued_at.unwrap() + drift);
1083        options.time_tolerance = Some(Duration::from_mins(1));
1084        assert!(claims.validate(&options).is_err());
1085
1086        // Verifier clock is 2 minutes ahead of the token clock.
1087        // The token is valid for 30 seconds, with an extra tolerance of 1 minute.
1088        // Verification must not pass.
1089        let drift = Duration::from_secs(30);
1090        options.artificial_time = Some(claims.issued_at.unwrap() + drift);
1091        options.time_tolerance = Some(Duration::from_mins(1));
1092        claims.validate(&options).unwrap();
1093
1094        // Verifier clock is 2 minutes behind the token clock.
1095        // The token is valid for 1 minute, so it is already expired.
1096        // We have a tolerance of 1 minute.
1097        // Verification must not pass.
1098        let drift = Duration::from_mins(2);
1099        options.artificial_time = Some(claims.issued_at.unwrap() - drift);
1100        options.time_tolerance = Some(Duration::from_mins(1));
1101        assert!(claims.validate(&options).is_err());
1102
1103        // Verifier clock is 2 minutes behind the token clock.
1104        // The token is valid for 1 minute, so it is already expired.
1105        // We have a tolerance of 2 minute.
1106        // Verification should pass.
1107        let drift = Duration::from_mins(2);
1108        options.artificial_time = Some(claims.issued_at.unwrap() - drift);
1109        options.time_tolerance = Some(Duration::from_mins(2));
1110        claims.validate(&options).unwrap();
1111    }
1112
1113    #[test]
1114    fn accept_future_allows_issued_at_in_future() {
1115        let mut claims = Claims::create(Duration::from_mins(30));
1116        let now = claims.issued_at.unwrap();
1117        let future = now + Duration::from_mins(10);
1118        claims.issued_at = Some(future);
1119        claims.invalid_before = Some(now);
1120
1121        let mut options = VerificationOptions::default();
1122        options.artificial_time = Some(now);
1123        options.time_tolerance = Some(Duration::from_secs(0));
1124
1125        options.accept_future = true;
1126        claims.validate(&options).unwrap();
1127
1128        options.accept_future = false;
1129        assert!(matches!(
1130            claims
1131                .validate(&options)
1132                .unwrap_err()
1133                .downcast_ref::<crate::JWTError>(),
1134            Some(crate::JWTError::ClockDrift)
1135        ));
1136    }
1137
1138    #[test]
1139    fn debug_displays_jwt_id_correctly() {
1140        let exp = Duration::from_mins(10);
1141
1142        // Test valid UTF-8
1143        let claims1 = Claims::create(exp).with_jwt_id("valid-utf8-jwt-id");
1144        let debug_str1 = format!("{:?}", claims1);
1145        assert!(debug_str1.contains("jwt_id: Some(\"valid-utf8-jwt-id\")"));
1146
1147        // Create a binary JWT ID containing bytes that cannot be represented as valid UTF-8
1148        // We'll use a base64-encoded string with deliberately non-UTF8 bytes
1149        let binary_jwt_id =
1150            Base64UrlSafeNoPadding::encode_to_string(vec![0xff, 0x00, 0xfe, 0x7f]).unwrap();
1151
1152        // Create claims with the JWT ID containing binary data
1153        let claims2 = Claims::create(exp).with_jwt_id(binary_jwt_id);
1154
1155        // We need to modify the test assertion. Since we're representing the binary data
1156        // with a valid base64-encoded string (which is valid UTF-8), it will be displayed
1157        // as a regular string, not as hex. However, we still want to check that the Debug
1158        // implementation works correctly.
1159        let debug_str2 = format!("{:?}", claims2);
1160
1161        // The JWT ID will be displayed normally, so we'll just check the basic formatting
1162        assert!(debug_str2.contains("jwt_id: Some("));
1163    }
1164}