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