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}