Skip to main content

jsonwebtoken/
validation.rs

1use std::borrow::Cow;
2use std::collections::HashSet;
3use std::fmt;
4use std::marker::PhantomData;
5
6use serde::de::{self, Visitor};
7use serde::{Deserialize, Deserializer};
8
9use crate::algorithms::Algorithm;
10use 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)]
31pub 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
111impl Validation {
112    /// Create a default validation setup allowing the given alg
113    pub fn new(alg: Algorithm) -> Validation {
114        let mut required_claims = HashSet::with_capacity(1);
115        required_claims.insert("exp".to_owned());
116
117        Validation {
118            required_spec_claims: required_claims,
119            algorithms: vec![alg],
120            leeway: 60,
121            reject_tokens_expiring_in_less_than: 0,
122
123            validate_exp: true,
124            validate_nbf: false,
125            validate_aud: true,
126
127            iss: None,
128            sub: None,
129            aud: None,
130
131            validate_signature: true,
132        }
133    }
134
135    /// `aud` is a collection of one or more acceptable audience members
136    /// The simple usage is `set_audience(&["some aud name"])`
137    pub fn set_audience<T: ToString>(&mut self, items: &[T]) {
138        self.aud = Some(items.iter().map(|x| x.to_string()).collect())
139    }
140
141    /// `iss` is a collection of one or more acceptable issuers members
142    /// The simple usage is `set_issuer(&["some iss name"])`
143    pub fn set_issuer<T: ToString>(&mut self, items: &[T]) {
144        self.iss = Some(items.iter().map(|x| x.to_string()).collect())
145    }
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    pub fn set_required_spec_claims<T: ToString>(&mut self, items: &[T]) {
153        self.required_spec_claims = items.iter().map(|x| x.to_string()).collect();
154    }
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    pub fn insecure_disable_signature_validation(&mut self) {
164        self.validate_signature = false;
165    }
166}
167
168impl Default for Validation {
169    fn default() -> Self {
170        Self::new(Algorithm::HS256)
171    }
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]
177pub fn get_current_timestamp() -> u64 {
178    let start = std::time::SystemTime::now();
179    start.duration_since(std::time::UNIX_EPOCH).expect("Time went backwards").as_secs()
180}
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]
185pub fn get_current_timestamp() -> u64 {
186    js_sys::Date::new_0().get_time() as u64 / 1000
187}
188
189#[derive(Deserialize)]
190pub(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)]
204enum TryParse<T> {
205    Parsed(T),
206    FailedToParse,
207    #[default]
208    NotPresent,
209}
210
211impl<'de, T: Deserialize<'de>> Deserialize<'de> for TryParse<T> {
212    fn deserialize<D: serde::Deserializer<'de>>(
213        deserializer: D,
214    ) -> std::result::Result<Self, D::Error> {
215        Ok(match Option::<T>::deserialize(deserializer) {
216            Ok(Some(value)) => TryParse::Parsed(value),
217            Ok(None) => TryParse::NotPresent,
218            Err(_) => TryParse::FailedToParse,
219        })
220    }
221}
222
223#[derive(Deserialize)]
224#[serde(untagged)]
225enum Audience<'a> {
226    Single(#[serde(borrow)] Cow<'a, str>),
227    Multiple(#[serde(borrow)] HashSet<BorrowedCowIfPossible<'a>>),
228}
229
230#[derive(Deserialize)]
231#[serde(untagged)]
232enum 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)]
241struct BorrowedCowIfPossible<'a>(#[serde(borrow)] Cow<'a, str>);
242
243impl std::borrow::Borrow<str> for BorrowedCowIfPossible<'_> {
244    fn borrow(&self) -> &str {
245        &self.0
246    }
247}
248
249fn is_subset(reference: &HashSet<String>, given: &HashSet<BorrowedCowIfPossible<'_>>) -> bool {
250    // Check that intersection is non-empty, favoring iterating on smallest
251    if reference.len() < given.len() {
252        reference.iter().any(|a| given.contains(&**a))
253    } else {
254        given.iter().any(|a| reference.contains(&*a.0))
255    }
256}
257
258pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Result<()> {
259    for required_claim in &options.required_spec_claims {
260        let present = match required_claim.as_str() {
261            "exp" => matches!(claims.exp, TryParse::Parsed(_)),
262            "sub" => matches!(claims.sub, TryParse::Parsed(_)),
263            "iss" => matches!(claims.iss, TryParse::Parsed(_)),
264            "aud" => matches!(claims.aud, TryParse::Parsed(_)),
265            "nbf" => matches!(claims.nbf, TryParse::Parsed(_)),
266            _ => continue,
267        };
268
269        if !present {
270            return Err(new_error(ErrorKind::MissingRequiredClaim(required_claim.clone())));
271        }
272    }
273
274    if options.validate_exp || options.validate_nbf {
275        let now = get_current_timestamp();
276
277        // Reject malformed exp/nbf claim when validation is enabled
278        if options.validate_exp && matches!(claims.exp, TryParse::FailedToParse) {
279            return Err(new_error(ErrorKind::InvalidClaimFormat("exp".to_string())));
280        }
281        if options.validate_nbf && matches!(claims.nbf, TryParse::FailedToParse) {
282            return Err(new_error(ErrorKind::InvalidClaimFormat("nbf".to_string())));
283        }
284
285        if matches!(claims.exp, TryParse::Parsed(exp) if exp < options.reject_tokens_expiring_in_less_than)
286        {
287            return Err(new_error(ErrorKind::InvalidToken));
288        }
289
290        if matches!(claims.exp, TryParse::Parsed(exp) if options.validate_exp
291            && exp - options.reject_tokens_expiring_in_less_than < now - options.leeway )
292        {
293            return Err(new_error(ErrorKind::ExpiredSignature));
294        }
295
296        if matches!(claims.nbf, TryParse::Parsed(nbf) if options.validate_nbf && nbf > now + options.leeway)
297        {
298            return Err(new_error(ErrorKind::ImmatureSignature));
299        }
300    }
301
302    if let (TryParse::Parsed(sub), Some(correct_sub)) = (claims.sub, options.sub.as_deref()) {
303        if sub != correct_sub {
304            return Err(new_error(ErrorKind::InvalidSubject));
305        }
306    }
307
308    match (claims.iss, options.iss.as_ref()) {
309        (TryParse::Parsed(Issuer::Single(iss)), Some(correct_iss)) => {
310            if !correct_iss.contains(&*iss) {
311                return Err(new_error(ErrorKind::InvalidIssuer));
312            }
313        }
314        (TryParse::Parsed(Issuer::Multiple(iss)), Some(correct_iss)) => {
315            if !is_subset(correct_iss, &iss) {
316                return Err(new_error(ErrorKind::InvalidIssuer));
317            }
318        }
319        _ => {}
320    }
321
322    if !options.validate_aud {
323        return Ok(());
324    }
325    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        (TryParse::Parsed(Audience::Multiple(aud)), None) => {
332            if !aud.is_empty() {
333                return Err(new_error(ErrorKind::InvalidAudience));
334            }
335        }
336        (TryParse::Parsed(_), None) => {
337            return Err(new_error(ErrorKind::InvalidAudience));
338        }
339        (TryParse::Parsed(Audience::Single(aud)), Some(correct_aud)) => {
340            if !correct_aud.contains(&*aud) {
341                return Err(new_error(ErrorKind::InvalidAudience));
342            }
343        }
344        (TryParse::Parsed(Audience::Multiple(aud)), Some(correct_aud)) => {
345            if !is_subset(correct_aud, &aud) {
346                return Err(new_error(ErrorKind::InvalidAudience));
347            }
348        }
349        _ => {}
350    }
351
352    Ok(())
353}
354
355fn numeric_type<'de, D>(deserializer: D) -> std::result::Result<TryParse<u64>, D::Error>
356where
357    D: Deserializer<'de>,
358{
359    struct NumericType(PhantomData<fn() -> TryParse<u64>>);
360
361    impl Visitor<'_> for NumericType {
362        type Value = TryParse<u64>;
363
364        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
365            formatter.write_str("A NumericType that can be reasonably coerced into a u64")
366        }
367
368        fn visit_u64<E>(self, value: u64) -> std::result::Result<Self::Value, E>
369        where
370            E: de::Error,
371        {
372            Ok(TryParse::Parsed(value))
373        }
374
375        fn visit_f64<E>(self, value: f64) -> std::result::Result<Self::Value, E>
376        where
377            E: de::Error,
378        {
379            if value.is_finite() && value >= 0.0 && value < (u64::MAX as f64) {
380                Ok(TryParse::Parsed(value.round() as u64))
381            } else {
382                Err(serde::de::Error::custom("NumericType must be representable as a u64"))
383            }
384        }
385    }
386
387    match deserializer.deserialize_any(NumericType(PhantomData)) {
388        Ok(ok) => Ok(ok),
389        Err(_) => Ok(TryParse::FailedToParse),
390    }
391}
392
393#[cfg(test)]
394mod 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}