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