Coverage Report

Created: 2026-03-11 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jsonwebtoken-10.3.0/src/validation.rs
Line
Count
Source
1
use std::borrow::Cow;
2
use std::collections::HashSet;
3
use std::fmt;
4
use std::marker::PhantomData;
5
6
use serde::de::{self, Visitor};
7
use serde::{Deserialize, Deserializer};
8
9
use crate::algorithms::Algorithm;
10
use crate::errors::{ErrorKind, Result, new_error};
11
12
/// Contains the various validations that are applied after decoding a JWT.
13
///
14
/// All time validation happen on UTC timestamps as seconds.
15
///
16
/// ```rust
17
/// use jsonwebtoken::{Validation, Algorithm};
18
///
19
/// let mut validation = Validation::new(Algorithm::HS256);
20
/// validation.leeway = 5;
21
/// // Setting audience
22
/// validation.set_audience(&["Me"]); // a single string
23
/// validation.set_audience(&["Me", "You"]); // array of strings
24
/// // or issuer
25
/// validation.set_issuer(&["Me"]); // a single string
26
/// validation.set_issuer(&["Me", "You"]); // array of strings
27
/// // Setting required claims
28
/// validation.set_required_spec_claims(&["exp", "iss", "aud"]);
29
/// ```
30
#[derive(Debug, Clone, PartialEq, Eq)]
31
pub struct Validation {
32
    /// Which claims are required to be present before starting the validation.
33
    /// This does not interact with the various `validate_*`. If you remove `exp` from that list, you still need
34
    /// to set `validate_exp` to `false`.
35
    /// The only value that will be used are "exp", "nbf", "aud", "iss", "sub". Anything else will be ignored.
36
    ///
37
    /// Defaults to `{"exp"}`
38
    pub required_spec_claims: HashSet<String>,
39
    /// Add some leeway (in seconds) to the `exp` and `nbf` validation to
40
    /// account for clock skew.
41
    ///
42
    /// Defaults to `60`.
43
    pub leeway: u64,
44
    /// Reject a token some time (in seconds) before the `exp` to prevent
45
    /// expiration in transit over the network.
46
    ///
47
    /// The value is the inverse of `leeway`, subtracting from the validation time.
48
    ///
49
    /// Defaults to `0`.
50
    pub reject_tokens_expiring_in_less_than: u64,
51
    /// Whether to validate the `exp` field.
52
    ///
53
    /// It will return an error if the time in the `exp` field is past.
54
    ///
55
    /// Defaults to `true`.
56
    pub validate_exp: bool,
57
    /// Whether to validate the `nbf` field.
58
    ///
59
    /// It will return an error if the current timestamp is before the time in the `nbf` field.
60
    ///
61
    /// Validation only happens if `nbf` claim is present in the token.
62
    /// Adding `nbf` to `required_spec_claims` will make it required.
63
    ///
64
    /// Defaults to `false`.
65
    pub validate_nbf: bool,
66
    /// Whether to validate the `aud` field.
67
    ///
68
    /// It will return an error if the `aud` field is not a member of the audience provided.
69
    ///
70
    /// Validation only happens if `aud` claim is present in the token.
71
    /// Adding `aud` to `required_spec_claims` will make it required.
72
    ///
73
    /// Defaults to `true`. Very insecure to turn this off. Only do this if you know what you are doing.
74
    pub validate_aud: bool,
75
    /// Validation will check that the `aud` field is a member of the
76
    /// audience provided and will error otherwise.
77
    /// Use `set_audience` to set it
78
    ///
79
    /// Validation only happens if `aud` claim is present in the token.
80
    /// Adding `aud` to `required_spec_claims` will make it required.
81
    ///
82
    /// Defaults to `None`.
83
    pub aud: Option<HashSet<String>>,
84
    /// If it contains a value, the validation will check that the `iss` field is a member of the
85
    /// iss provided and will error otherwise.
86
    /// Use `set_issuer` to set it
87
    ///
88
    /// Validation only happens if `iss` claim is present in the token.
89
    /// Adding `iss` to `required_spec_claims` will make it required.
90
    ///
91
    /// Defaults to `None`.
92
    pub iss: Option<HashSet<String>>,
93
    /// If it contains a value, the validation will check that the `sub` field is the same as the
94
    /// one provided and will error otherwise.
95
    ///
96
    /// Validation only happens if `sub` claim is present in the token.
97
    /// Adding `sub` to `required_spec_claims` will make it required.
98
    ///
99
    /// Defaults to `None`.
100
    pub sub: Option<String>,
101
    /// The validation will check that the `alg` of the header is contained
102
    /// in the ones provided and will error otherwise. Will error if it is empty.
103
    ///
104
    /// Defaults to `vec![Algorithm::HS256]`.
105
    pub algorithms: Vec<Algorithm>,
106
107
    /// Whether to validate the JWT signature. Very insecure to turn that off
108
    pub(crate) validate_signature: bool,
109
}
110
111
impl Validation {
112
    /// Create a default validation setup allowing the given alg
113
0
    pub fn new(alg: Algorithm) -> Validation {
114
0
        let mut required_claims = HashSet::with_capacity(1);
115
0
        required_claims.insert("exp".to_owned());
116
117
0
        Validation {
118
0
            required_spec_claims: required_claims,
119
0
            algorithms: vec![alg],
120
0
            leeway: 60,
121
0
            reject_tokens_expiring_in_less_than: 0,
122
0
123
0
            validate_exp: true,
124
0
            validate_nbf: false,
125
0
            validate_aud: true,
126
0
127
0
            iss: None,
128
0
            sub: None,
129
0
            aud: None,
130
0
131
0
            validate_signature: true,
132
0
        }
133
0
    }
134
135
    /// `aud` is a collection of one or more acceptable audience members
136
    /// The simple usage is `set_audience(&["some aud name"])`
137
0
    pub fn set_audience<T: ToString>(&mut self, items: &[T]) {
138
0
        self.aud = Some(items.iter().map(|x| x.to_string()).collect())
139
0
    }
140
141
    /// `iss` is a collection of one or more acceptable issuers members
142
    /// The simple usage is `set_issuer(&["some iss name"])`
143
0
    pub fn set_issuer<T: ToString>(&mut self, items: &[T]) {
144
0
        self.iss = Some(items.iter().map(|x| x.to_string()).collect())
145
0
    }
146
147
    /// Which claims are required to be present for this JWT to be considered valid.
148
    /// The only values that will be considered are "exp", "nbf", "aud", "iss", "sub".
149
    /// The simple usage is `set_required_spec_claims(&["exp", "nbf"])`.
150
    /// If you want to have an empty set, do not use this function - set an empty set on the struct
151
    /// param directly.
152
0
    pub fn set_required_spec_claims<T: ToString>(&mut self, items: &[T]) {
153
0
        self.required_spec_claims = items.iter().map(|x| x.to_string()).collect();
154
0
    }
155
156
    /// Whether to validate the JWT cryptographic signature.
157
    /// Disabling validation is dangerous, only do it if you know what you're doing.
158
    /// With validation disabled you should not trust any of the values of the claims.
159
    #[deprecated(
160
        since = "10.1.0",
161
        note = "Use `jsonwebtoken::dangerous::insecure_decode` if you require this functionality."
162
    )]
163
0
    pub fn insecure_disable_signature_validation(&mut self) {
164
0
        self.validate_signature = false;
165
0
    }
166
}
167
168
impl Default for Validation {
169
0
    fn default() -> Self {
170
0
        Self::new(Algorithm::HS256)
171
0
    }
172
}
173
174
/// Gets the current timestamp in the format expected by JWTs.
175
#[cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))]
176
#[must_use]
177
0
pub fn get_current_timestamp() -> u64 {
178
0
    let start = std::time::SystemTime::now();
179
0
    start.duration_since(std::time::UNIX_EPOCH).expect("Time went backwards").as_secs()
180
0
}
181
182
/// Gets the current timestamp in the format expected by JWTs.
183
#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))]
184
#[must_use]
185
pub fn get_current_timestamp() -> u64 {
186
    js_sys::Date::new_0().get_time() as u64 / 1000
187
}
188
189
#[derive(Deserialize)]
190
pub(crate) struct ClaimsForValidation<'a> {
191
    #[serde(deserialize_with = "numeric_type", default)]
192
    exp: TryParse<u64>,
193
    #[serde(deserialize_with = "numeric_type", default)]
194
    nbf: TryParse<u64>,
195
    #[serde(borrow)]
196
    sub: TryParse<Cow<'a, str>>,
197
    #[serde(borrow)]
198
    iss: TryParse<Issuer<'a>>,
199
    #[serde(borrow)]
200
    aud: TryParse<Audience<'a>>,
201
}
202
203
#[derive(Default, Debug)]
204
enum TryParse<T> {
205
    Parsed(T),
206
    FailedToParse,
207
    #[default]
208
    NotPresent,
209
}
210
211
impl<'de, T: Deserialize<'de>> Deserialize<'de> for TryParse<T> {
212
0
    fn deserialize<D: serde::Deserializer<'de>>(
213
0
        deserializer: D,
214
0
    ) -> std::result::Result<Self, D::Error> {
215
0
        Ok(match Option::<T>::deserialize(deserializer) {
216
0
            Ok(Some(value)) => TryParse::Parsed(value),
217
0
            Ok(None) => TryParse::NotPresent,
218
0
            Err(_) => TryParse::FailedToParse,
219
        })
220
0
    }
Unexecuted instantiation: <jsonwebtoken::validation::TryParse<alloc::borrow::Cow<str>> as serde_core::de::Deserialize>::deserialize::<serde::private::de::missing_field::MissingFieldDeserializer<serde_json::error::Error>>
Unexecuted instantiation: <jsonwebtoken::validation::TryParse<alloc::borrow::Cow<str>> as serde_core::de::Deserialize>::deserialize::<&mut serde_json::de::Deserializer<serde_json::read::SliceRead>>
Unexecuted instantiation: <jsonwebtoken::validation::TryParse<jsonwebtoken::validation::Issuer> as serde_core::de::Deserialize>::deserialize::<serde::private::de::missing_field::MissingFieldDeserializer<serde_json::error::Error>>
Unexecuted instantiation: <jsonwebtoken::validation::TryParse<jsonwebtoken::validation::Issuer> as serde_core::de::Deserialize>::deserialize::<&mut serde_json::de::Deserializer<serde_json::read::SliceRead>>
Unexecuted instantiation: <jsonwebtoken::validation::TryParse<jsonwebtoken::validation::Audience> as serde_core::de::Deserialize>::deserialize::<serde::private::de::missing_field::MissingFieldDeserializer<serde_json::error::Error>>
Unexecuted instantiation: <jsonwebtoken::validation::TryParse<jsonwebtoken::validation::Audience> as serde_core::de::Deserialize>::deserialize::<&mut serde_json::de::Deserializer<serde_json::read::SliceRead>>
Unexecuted instantiation: <jsonwebtoken::validation::TryParse<_> as serde_core::de::Deserialize>::deserialize::<_>
221
}
222
223
#[derive(Deserialize)]
224
#[serde(untagged)]
225
enum Audience<'a> {
226
    Single(#[serde(borrow)] Cow<'a, str>),
227
    Multiple(#[serde(borrow)] HashSet<BorrowedCowIfPossible<'a>>),
228
}
229
230
#[derive(Deserialize)]
231
#[serde(untagged)]
232
enum Issuer<'a> {
233
    Single(#[serde(borrow)] Cow<'a, str>),
234
    Multiple(#[serde(borrow)] HashSet<BorrowedCowIfPossible<'a>>),
235
}
236
237
/// Usually #[serde(borrow)] on `Cow` enables deserializing with no allocations where
238
/// possible (no escapes in the original str) but it does not work on e.g. `HashSet<Cow<str>>`
239
/// We use this struct in this case.
240
#[derive(Deserialize, PartialEq, Eq, Hash)]
241
struct BorrowedCowIfPossible<'a>(#[serde(borrow)] Cow<'a, str>);
242
243
impl std::borrow::Borrow<str> for BorrowedCowIfPossible<'_> {
244
0
    fn borrow(&self) -> &str {
245
0
        &self.0
246
0
    }
247
}
248
249
0
fn is_subset(reference: &HashSet<String>, given: &HashSet<BorrowedCowIfPossible<'_>>) -> bool {
250
    // Check that intersection is non-empty, favoring iterating on smallest
251
0
    if reference.len() < given.len() {
252
0
        reference.iter().any(|a| given.contains(&**a))
253
    } else {
254
0
        given.iter().any(|a| reference.contains(&*a.0))
255
    }
256
0
}
257
258
0
pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Result<()> {
259
0
    for required_claim in &options.required_spec_claims {
260
0
        let present = match required_claim.as_str() {
261
0
            "exp" => matches!(claims.exp, TryParse::Parsed(_)),
262
0
            "sub" => matches!(claims.sub, TryParse::Parsed(_)),
263
0
            "iss" => matches!(claims.iss, TryParse::Parsed(_)),
264
0
            "aud" => matches!(claims.aud, TryParse::Parsed(_)),
265
0
            "nbf" => matches!(claims.nbf, TryParse::Parsed(_)),
266
0
            _ => continue,
267
        };
268
269
0
        if !present {
270
0
            return Err(new_error(ErrorKind::MissingRequiredClaim(required_claim.clone())));
271
0
        }
272
    }
273
274
0
    if options.validate_exp || options.validate_nbf {
275
0
        let now = get_current_timestamp();
276
277
        // Reject malformed exp/nbf claim when validation is enabled
278
0
        if options.validate_exp && matches!(claims.exp, TryParse::FailedToParse) {
279
0
            return Err(new_error(ErrorKind::InvalidClaimFormat("exp".to_string())));
280
0
        }
281
0
        if options.validate_nbf && matches!(claims.nbf, TryParse::FailedToParse) {
282
0
            return Err(new_error(ErrorKind::InvalidClaimFormat("nbf".to_string())));
283
0
        }
284
285
0
        if matches!(claims.exp, TryParse::Parsed(exp) if exp < options.reject_tokens_expiring_in_less_than)
286
        {
287
0
            return Err(new_error(ErrorKind::InvalidToken));
288
0
        }
289
290
0
        if matches!(claims.exp, TryParse::Parsed(exp) if options.validate_exp
291
0
            && exp - options.reject_tokens_expiring_in_less_than < now - options.leeway )
292
        {
293
0
            return Err(new_error(ErrorKind::ExpiredSignature));
294
0
        }
295
296
0
        if matches!(claims.nbf, TryParse::Parsed(nbf) if options.validate_nbf && nbf > now + options.leeway)
297
        {
298
0
            return Err(new_error(ErrorKind::ImmatureSignature));
299
0
        }
300
0
    }
301
302
0
    if let (TryParse::Parsed(sub), Some(correct_sub)) = (claims.sub, options.sub.as_deref()) {
303
0
        if sub != correct_sub {
304
0
            return Err(new_error(ErrorKind::InvalidSubject));
305
0
        }
306
0
    }
307
308
0
    match (claims.iss, options.iss.as_ref()) {
309
0
        (TryParse::Parsed(Issuer::Single(iss)), Some(correct_iss)) => {
310
0
            if !correct_iss.contains(&*iss) {
311
0
                return Err(new_error(ErrorKind::InvalidIssuer));
312
0
            }
313
        }
314
0
        (TryParse::Parsed(Issuer::Multiple(iss)), Some(correct_iss)) => {
315
0
            if !is_subset(correct_iss, &iss) {
316
0
                return Err(new_error(ErrorKind::InvalidIssuer));
317
0
            }
318
        }
319
0
        _ => {}
320
    }
321
322
0
    if !options.validate_aud {
323
0
        return Ok(());
324
0
    }
325
0
    match (claims.aud, options.aud.as_ref()) {
326
        // Each principal intended to process the JWT MUST
327
        // identify itself with a value in the audience claim. If the principal
328
        // processing the claim does not identify itself with a value in the
329
        // "aud" claim when this claim is present, then the JWT MUST be
330
        //  rejected.
331
0
        (TryParse::Parsed(Audience::Multiple(aud)), None) => {
332
0
            if !aud.is_empty() {
333
0
                return Err(new_error(ErrorKind::InvalidAudience));
334
0
            }
335
        }
336
        (TryParse::Parsed(_), None) => {
337
0
            return Err(new_error(ErrorKind::InvalidAudience));
338
        }
339
0
        (TryParse::Parsed(Audience::Single(aud)), Some(correct_aud)) => {
340
0
            if !correct_aud.contains(&*aud) {
341
0
                return Err(new_error(ErrorKind::InvalidAudience));
342
0
            }
343
        }
344
0
        (TryParse::Parsed(Audience::Multiple(aud)), Some(correct_aud)) => {
345
0
            if !is_subset(correct_aud, &aud) {
346
0
                return Err(new_error(ErrorKind::InvalidAudience));
347
0
            }
348
        }
349
0
        _ => {}
350
    }
351
352
0
    Ok(())
353
0
}
354
355
0
fn numeric_type<'de, D>(deserializer: D) -> std::result::Result<TryParse<u64>, D::Error>
356
0
where
357
0
    D: Deserializer<'de>,
358
{
359
    struct NumericType(PhantomData<fn() -> TryParse<u64>>);
360
361
    impl Visitor<'_> for NumericType {
362
        type Value = TryParse<u64>;
363
364
0
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
365
0
            formatter.write_str("A NumericType that can be reasonably coerced into a u64")
366
0
        }
367
368
0
        fn visit_u64<E>(self, value: u64) -> std::result::Result<Self::Value, E>
369
0
        where
370
0
            E: de::Error,
371
        {
372
0
            Ok(TryParse::Parsed(value))
373
0
        }
Unexecuted instantiation: <jsonwebtoken::validation::numeric_type::NumericType as serde_core::de::Visitor>::visit_u64::<serde_json::error::Error>
Unexecuted instantiation: <jsonwebtoken::validation::numeric_type::NumericType as serde_core::de::Visitor>::visit_u64::<_>
374
375
0
        fn visit_f64<E>(self, value: f64) -> std::result::Result<Self::Value, E>
376
0
        where
377
0
            E: de::Error,
378
        {
379
0
            if value.is_finite() && value >= 0.0 && value < (u64::MAX as f64) {
380
0
                Ok(TryParse::Parsed(value.round() as u64))
381
            } else {
382
0
                Err(serde::de::Error::custom("NumericType must be representable as a u64"))
383
            }
384
0
        }
Unexecuted instantiation: <jsonwebtoken::validation::numeric_type::NumericType as serde_core::de::Visitor>::visit_f64::<serde_json::error::Error>
Unexecuted instantiation: <jsonwebtoken::validation::numeric_type::NumericType as serde_core::de::Visitor>::visit_f64::<_>
385
    }
386
387
0
    match deserializer.deserialize_any(NumericType(PhantomData)) {
388
0
        Ok(ok) => Ok(ok),
389
0
        Err(_) => Ok(TryParse::FailedToParse),
390
    }
391
0
}
Unexecuted instantiation: jsonwebtoken::validation::numeric_type::<&mut serde_json::de::Deserializer<serde_json::read::SliceRead>>
Unexecuted instantiation: jsonwebtoken::validation::numeric_type::<_>
392
393
#[cfg(test)]
394
mod tests {
395
    use std::collections::HashSet;
396
397
    use serde_json::json;
398
    use wasm_bindgen_test::wasm_bindgen_test;
399
400
    use crate::Algorithm;
401
    use crate::errors::ErrorKind;
402
403
    use super::{ClaimsForValidation, Validation, get_current_timestamp, validate};
404
405
    fn deserialize_claims(claims: &serde_json::Value) -> ClaimsForValidation<'_> {
406
        serde::Deserialize::deserialize(claims).unwrap()
407
    }
408
409
    #[test]
410
    #[wasm_bindgen_test]
411
    fn exp_in_future_ok() {
412
        let claims = json!({ "exp": get_current_timestamp() + 10000 });
413
        let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
414
        assert!(res.is_ok());
415
    }
416
417
    #[test]
418
    #[wasm_bindgen_test]
419
    fn exp_in_future_but_in_rejection_period_fails() {
420
        let claims = json!({ "exp": get_current_timestamp() + 500 });
421
        let mut validation = Validation::new(Algorithm::HS256);
422
        validation.leeway = 0;
423
        validation.reject_tokens_expiring_in_less_than = 501;
424
        let res = validate(deserialize_claims(&claims), &validation);
425
        assert!(res.is_err());
426
    }
427
428
    #[test]
429
    #[wasm_bindgen_test]
430
    fn exp_float_in_future_ok() {
431
        let claims = json!({ "exp": (get_current_timestamp() as f64) + 10000.123 });
432
        let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
433
        assert!(res.is_ok());
434
    }
435
436
    #[test]
437
    #[wasm_bindgen_test]
438
    fn exp_float_in_future_but_in_rejection_period_fails() {
439
        let claims = json!({ "exp": (get_current_timestamp() as f64) + 500.123 });
440
        let mut validation = Validation::new(Algorithm::HS256);
441
        validation.leeway = 0;
442
        validation.reject_tokens_expiring_in_less_than = 501;
443
        let res = validate(deserialize_claims(&claims), &validation);
444
        assert!(res.is_err());
445
    }
446
447
    #[test]
448
    #[wasm_bindgen_test]
449
    fn exp_in_past_fails() {
450
        let claims = json!({ "exp": get_current_timestamp() - 100000 });
451
        let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
452
        assert!(res.is_err());
453
454
        match res.unwrap_err().kind() {
455
            ErrorKind::ExpiredSignature => (),
456
            _ => unreachable!(),
457
        };
458
    }
459
460
    #[test]
461
    #[wasm_bindgen_test]
462
    fn exp_float_in_past_fails() {
463
        let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 });
464
        let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
465
        assert!(res.is_err());
466
467
        match res.unwrap_err().kind() {
468
            ErrorKind::ExpiredSignature => (),
469
            _ => unreachable!(),
470
        };
471
    }
472
473
    #[test]
474
    #[wasm_bindgen_test]
475
    fn exp_in_past_but_in_leeway_ok() {
476
        let claims = json!({ "exp": get_current_timestamp() - 500 });
477
        let mut validation = Validation::new(Algorithm::HS256);
478
        validation.leeway = 1000 * 60;
479
        let res = validate(deserialize_claims(&claims), &validation);
480
        assert!(res.is_ok());
481
    }
482
483
    // https://github.com/Keats/jsonwebtoken/issues/51
484
    #[test]
485
    #[wasm_bindgen_test]
486
    fn validate_required_fields_are_present() {
487
        for spec_claim in ["exp", "nbf", "aud", "iss", "sub"] {
488
            let claims = json!({});
489
            let mut validation = Validation::new(Algorithm::HS256);
490
            validation.set_required_spec_claims(&[spec_claim]);
491
            let res = validate(deserialize_claims(&claims), &validation).unwrap_err();
492
            assert_eq!(res.kind(), &ErrorKind::MissingRequiredClaim(spec_claim.to_owned()));
493
        }
494
    }
495
496
    #[test]
497
    #[wasm_bindgen_test]
498
    fn exp_validated_but_not_required_ok() {
499
        let claims = json!({});
500
        let mut validation = Validation::new(Algorithm::HS256);
501
        validation.required_spec_claims = HashSet::new();
502
        validation.validate_exp = true;
503
        let res = validate(deserialize_claims(&claims), &validation);
504
        assert!(res.is_ok());
505
    }
506
507
    #[test]
508
    #[wasm_bindgen_test]
509
    fn exp_validated_but_not_required_fails() {
510
        let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 });
511
        let mut validation = Validation::new(Algorithm::HS256);
512
        validation.required_spec_claims = HashSet::new();
513
        validation.validate_exp = true;
514
        let res = validate(deserialize_claims(&claims), &validation);
515
        assert!(res.is_err());
516
    }
517
518
    #[test]
519
    #[wasm_bindgen_test]
520
    fn exp_required_but_not_validated_ok() {
521
        let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 });
522
        let mut validation = Validation::new(Algorithm::HS256);
523
        validation.set_required_spec_claims(&["exp"]);
524
        validation.validate_exp = false;
525
        let res = validate(deserialize_claims(&claims), &validation);
526
        assert!(res.is_ok());
527
    }
528
529
    #[test]
530
    #[wasm_bindgen_test]
531
    fn exp_required_but_not_validated_fails() {
532
        let claims = json!({});
533
        let mut validation = Validation::new(Algorithm::HS256);
534
        validation.set_required_spec_claims(&["exp"]);
535
        validation.validate_exp = false;
536
        let res = validate(deserialize_claims(&claims), &validation);
537
        assert!(res.is_err());
538
    }
539
540
    #[test]
541
    #[wasm_bindgen_test]
542
    fn nbf_in_past_ok() {
543
        let claims = json!({ "nbf": get_current_timestamp() - 10000 });
544
        let mut validation = Validation::new(Algorithm::HS256);
545
        validation.required_spec_claims = HashSet::new();
546
        validation.validate_exp = false;
547
        validation.validate_nbf = true;
548
        let res = validate(deserialize_claims(&claims), &validation);
549
        assert!(res.is_ok());
550
    }
551
552
    #[test]
553
    #[wasm_bindgen_test]
554
    fn nbf_float_in_past_ok() {
555
        let claims = json!({ "nbf": (get_current_timestamp() as f64) - 10000.1234 });
556
        let mut validation = Validation::new(Algorithm::HS256);
557
        validation.required_spec_claims = HashSet::new();
558
        validation.validate_exp = false;
559
        validation.validate_nbf = true;
560
        let res = validate(deserialize_claims(&claims), &validation);
561
        assert!(res.is_ok());
562
    }
563
564
    #[test]
565
    #[wasm_bindgen_test]
566
    fn nbf_in_future_fails() {
567
        let claims = json!({ "nbf": get_current_timestamp() + 100000 });
568
        let mut validation = Validation::new(Algorithm::HS256);
569
        validation.required_spec_claims = HashSet::new();
570
        validation.validate_exp = false;
571
        validation.validate_nbf = true;
572
        let res = validate(deserialize_claims(&claims), &validation);
573
        assert!(res.is_err());
574
575
        match res.unwrap_err().kind() {
576
            ErrorKind::ImmatureSignature => (),
577
            _ => unreachable!(),
578
        };
579
    }
580
581
    #[test]
582
    #[wasm_bindgen_test]
583
    fn nbf_in_future_but_in_leeway_ok() {
584
        let claims = json!({ "nbf": get_current_timestamp() + 500 });
585
        let mut validation = Validation::new(Algorithm::HS256);
586
        validation.required_spec_claims = HashSet::new();
587
        validation.validate_exp = false;
588
        validation.validate_nbf = true;
589
        validation.leeway = 1000 * 60;
590
        let res = validate(deserialize_claims(&claims), &validation);
591
        assert!(res.is_ok());
592
    }
593
594
    #[test]
595
    #[wasm_bindgen_test]
596
    fn iss_string_ok() {
597
        let claims = json!({"iss": ["Keats"]});
598
        let mut validation = Validation::new(Algorithm::HS256);
599
        validation.required_spec_claims = HashSet::new();
600
        validation.validate_exp = false;
601
        validation.set_issuer(&["Keats"]);
602
        let res = validate(deserialize_claims(&claims), &validation);
603
        assert!(res.is_ok());
604
    }
605
606
    #[test]
607
    #[wasm_bindgen_test]
608
    fn iss_array_of_string_ok() {
609
        let claims = json!({"iss": ["UserA", "UserB"]});
610
        let mut validation = Validation::new(Algorithm::HS256);
611
        validation.required_spec_claims = HashSet::new();
612
        validation.validate_exp = false;
613
        validation.set_issuer(&["UserA", "UserB"]);
614
        let res = validate(deserialize_claims(&claims), &validation);
615
        assert!(res.is_ok());
616
    }
617
618
    #[test]
619
    #[wasm_bindgen_test]
620
    fn iss_not_matching_fails() {
621
        let claims = json!({"iss": "Hacked"});
622
623
        let mut validation = Validation::new(Algorithm::HS256);
624
        validation.required_spec_claims = HashSet::new();
625
        validation.validate_exp = false;
626
        validation.set_issuer(&["Keats"]);
627
        let res = validate(deserialize_claims(&claims), &validation);
628
        assert!(res.is_err());
629
630
        match res.unwrap_err().kind() {
631
            ErrorKind::InvalidIssuer => (),
632
            _ => unreachable!(),
633
        };
634
    }
635
636
    #[test]
637
    #[wasm_bindgen_test]
638
    fn iss_missing_fails() {
639
        let claims = json!({});
640
641
        let mut validation = Validation::new(Algorithm::HS256);
642
        validation.set_required_spec_claims(&["iss"]);
643
        validation.validate_exp = false;
644
        validation.set_issuer(&["Keats"]);
645
        let res = validate(deserialize_claims(&claims), &validation);
646
647
        match res.unwrap_err().kind() {
648
            ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "iss"),
649
            _ => unreachable!(),
650
        };
651
    }
652
653
    #[test]
654
    #[wasm_bindgen_test]
655
    fn sub_ok() {
656
        let claims = json!({"sub": "Keats"});
657
        let mut validation = Validation::new(Algorithm::HS256);
658
        validation.required_spec_claims = HashSet::new();
659
        validation.validate_exp = false;
660
        validation.sub = Some("Keats".to_owned());
661
        let res = validate(deserialize_claims(&claims), &validation);
662
        assert!(res.is_ok());
663
    }
664
665
    #[test]
666
    #[wasm_bindgen_test]
667
    fn sub_not_matching_fails() {
668
        let claims = json!({"sub": "Hacked"});
669
        let mut validation = Validation::new(Algorithm::HS256);
670
        validation.required_spec_claims = HashSet::new();
671
        validation.validate_exp = false;
672
        validation.sub = Some("Keats".to_owned());
673
        let res = validate(deserialize_claims(&claims), &validation);
674
        assert!(res.is_err());
675
676
        match res.unwrap_err().kind() {
677
            ErrorKind::InvalidSubject => (),
678
            _ => unreachable!(),
679
        };
680
    }
681
682
    #[test]
683
    #[wasm_bindgen_test]
684
    fn sub_missing_fails() {
685
        let claims = json!({});
686
        let mut validation = Validation::new(Algorithm::HS256);
687
        validation.validate_exp = false;
688
        validation.set_required_spec_claims(&["sub"]);
689
        validation.sub = Some("Keats".to_owned());
690
        let res = validate(deserialize_claims(&claims), &validation);
691
        assert!(res.is_err());
692
693
        match res.unwrap_err().kind() {
694
            ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "sub"),
695
            _ => unreachable!(),
696
        };
697
    }
698
699
    #[test]
700
    #[wasm_bindgen_test]
701
    fn aud_string_ok() {
702
        let claims = json!({"aud": "Everyone"});
703
        let mut validation = Validation::new(Algorithm::HS256);
704
        validation.validate_exp = false;
705
        validation.required_spec_claims = HashSet::new();
706
        validation.set_audience(&["Everyone"]);
707
        let res = validate(deserialize_claims(&claims), &validation);
708
        assert!(res.is_ok());
709
    }
710
711
    #[test]
712
    #[wasm_bindgen_test]
713
    fn aud_array_of_string_ok() {
714
        let claims = json!({"aud": ["UserA", "UserB"]});
715
        let mut validation = Validation::new(Algorithm::HS256);
716
        validation.validate_exp = false;
717
        validation.required_spec_claims = HashSet::new();
718
        validation.set_audience(&["UserA", "UserB"]);
719
        let res = validate(deserialize_claims(&claims), &validation);
720
        assert!(res.is_ok());
721
    }
722
723
    #[test]
724
    #[wasm_bindgen_test]
725
    fn aud_type_mismatch_fails() {
726
        let claims = json!({"aud": ["Everyone"]});
727
        let mut validation = Validation::new(Algorithm::HS256);
728
        validation.validate_exp = false;
729
        validation.required_spec_claims = HashSet::new();
730
        validation.set_audience(&["UserA", "UserB"]);
731
        let res = validate(deserialize_claims(&claims), &validation);
732
        assert!(res.is_err());
733
734
        match res.unwrap_err().kind() {
735
            ErrorKind::InvalidAudience => (),
736
            _ => unreachable!(),
737
        };
738
    }
739
740
    #[test]
741
    #[wasm_bindgen_test]
742
    fn aud_correct_type_not_matching_fails() {
743
        let claims = json!({"aud": ["Everyone"]});
744
        let mut validation = Validation::new(Algorithm::HS256);
745
        validation.validate_exp = false;
746
        validation.required_spec_claims = HashSet::new();
747
        validation.set_audience(&["None"]);
748
        let res = validate(deserialize_claims(&claims), &validation);
749
        assert!(res.is_err());
750
751
        match res.unwrap_err().kind() {
752
            ErrorKind::InvalidAudience => (),
753
            _ => unreachable!(),
754
        };
755
    }
756
757
    #[test]
758
    #[wasm_bindgen_test]
759
    fn aud_none_fails() {
760
        let claims = json!({"aud": ["Everyone"]});
761
        let mut validation = Validation::new(Algorithm::HS256);
762
        validation.validate_exp = false;
763
        validation.required_spec_claims = HashSet::new();
764
        validation.aud = None;
765
        let res = validate(deserialize_claims(&claims), &validation);
766
        assert!(res.is_err());
767
768
        match res.unwrap_err().kind() {
769
            ErrorKind::InvalidAudience => (),
770
            _ => unreachable!(),
771
        };
772
    }
773
774
    #[test]
775
    #[wasm_bindgen_test]
776
    fn aud_validation_skipped() {
777
        let claims = json!({"aud": ["Everyone"]});
778
        let mut validation = Validation::new(Algorithm::HS256);
779
        validation.validate_exp = false;
780
        validation.validate_aud = false;
781
        validation.required_spec_claims = HashSet::new();
782
        validation.aud = None;
783
        let res = validate(deserialize_claims(&claims), &validation);
784
        assert!(res.is_ok());
785
    }
786
787
    #[test]
788
    #[wasm_bindgen_test]
789
    fn aud_missing_fails() {
790
        let claims = json!({});
791
        let mut validation = Validation::new(Algorithm::HS256);
792
        validation.validate_exp = false;
793
        validation.set_required_spec_claims(&["aud"]);
794
        validation.set_audience(&["None"]);
795
        let res = validate(deserialize_claims(&claims), &validation);
796
        assert!(res.is_err());
797
798
        match res.unwrap_err().kind() {
799
            ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "aud"),
800
            _ => unreachable!(),
801
        };
802
    }
803
804
    // https://github.com/Keats/jsonwebtoken/issues/51
805
    #[test]
806
    #[wasm_bindgen_test]
807
    fn does_validation_in_right_order() {
808
        let claims = json!({ "exp": get_current_timestamp() + 10000 });
809
810
        let mut validation = Validation::new(Algorithm::HS256);
811
        validation.set_required_spec_claims(&["exp", "iss"]);
812
        validation.leeway = 5;
813
        validation.set_issuer(&["iss no check"]);
814
        validation.set_audience(&["iss no check"]);
815
816
        let res = validate(deserialize_claims(&claims), &validation);
817
        // It errors because it needs to validate iss/sub which are missing
818
        assert!(res.is_err());
819
        match res.unwrap_err().kind() {
820
            ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "iss"),
821
            t => panic!("{:?}", t),
822
        };
823
    }
824
825
    // https://github.com/Keats/jsonwebtoken/issues/110
826
    #[test]
827
    #[wasm_bindgen_test]
828
    fn aud_use_validation_struct() {
829
        let claims = json!({"aud": "my-googleclientid1234.apps.googleusercontent.com"});
830
831
        let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string();
832
        let mut aud_hashset = std::collections::HashSet::new();
833
        aud_hashset.insert(aud);
834
        let mut validation = Validation::new(Algorithm::HS256);
835
        validation.validate_exp = false;
836
        validation.required_spec_claims = HashSet::new();
837
        validation.set_audience(&["my-googleclientid1234.apps.googleusercontent.com"]);
838
839
        let res = validate(deserialize_claims(&claims), &validation);
840
        assert!(res.is_ok());
841
    }
842
843
    // https://github.com/Keats/jsonwebtoken/issues/388
844
    #[test]
845
    #[wasm_bindgen_test]
846
    fn doesnt_panic_with_leeway_overflow() {
847
        let claims = json!({ "exp": 1 });
848
849
        let mut validation = Validation::new(Algorithm::HS256);
850
        validation.reject_tokens_expiring_in_less_than = 100;
851
852
        let res = validate(deserialize_claims(&claims), &validation);
853
        assert!(res.is_err());
854
    }
855
}