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