jwt/
claims.rs

1//! Convenience structs for commonly defined fields in claims.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7/// Generic [JWT claims](https://tools.ietf.org/html/rfc7519#page-8) with
8/// defined fields for registered and private claims.
9#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
10pub struct Claims {
11    #[serde(flatten)]
12    pub registered: RegisteredClaims,
13    #[serde(flatten)]
14    pub private: BTreeMap<String, serde_json::Value>,
15}
16
17impl Claims {
18    pub fn new(registered: RegisteredClaims) -> Self {
19        Claims {
20            registered,
21            private: BTreeMap::new(),
22        }
23    }
24}
25
26pub type SecondsSinceEpoch = u64;
27
28/// Registered claims according to the
29/// [JWT specification](https://tools.ietf.org/html/rfc7519#page-9).
30#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
31pub struct RegisteredClaims {
32    #[serde(rename = "iss", skip_serializing_if = "Option::is_none")]
33    pub issuer: Option<String>,
34
35    #[serde(rename = "sub", skip_serializing_if = "Option::is_none")]
36    pub subject: Option<String>,
37
38    #[serde(rename = "aud", skip_serializing_if = "Option::is_none")]
39    pub audience: Option<String>,
40
41    #[serde(rename = "exp", skip_serializing_if = "Option::is_none")]
42    pub expiration: Option<SecondsSinceEpoch>,
43
44    #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")]
45    pub not_before: Option<SecondsSinceEpoch>,
46
47    #[serde(rename = "iat", skip_serializing_if = "Option::is_none")]
48    pub issued_at: Option<SecondsSinceEpoch>,
49
50    #[serde(rename = "jti", skip_serializing_if = "Option::is_none")]
51    pub json_web_token_id: Option<String>,
52}
53
54#[cfg(test)]
55mod tests {
56    use crate::claims::Claims;
57    use crate::error::Error;
58    use crate::{FromBase64, ToBase64};
59    use serde_json::Value;
60    use std::default::Default;
61
62    // {"iss":"mikkyang.com","exp":1302319100,"custom_claim":true}
63    const ENCODED_PAYLOAD: &str =
64        "eyJpc3MiOiJtaWtreWFuZy5jb20iLCJleHAiOjEzMDIzMTkxMDAsImN1c3RvbV9jbGFpbSI6dHJ1ZX0K";
65
66    #[test]
67    fn registered_claims() -> Result<(), Error> {
68        let claims = Claims::from_base64(ENCODED_PAYLOAD)?;
69
70        assert_eq!(claims.registered.issuer.unwrap(), "mikkyang.com");
71        assert_eq!(claims.registered.expiration.unwrap(), 1302319100);
72        Ok(())
73    }
74
75    #[test]
76    fn private_claims() -> Result<(), Error> {
77        let claims = Claims::from_base64(ENCODED_PAYLOAD)?;
78
79        assert_eq!(claims.private["custom_claim"], Value::Bool(true));
80        Ok(())
81    }
82
83    #[test]
84    fn roundtrip() -> Result<(), Error> {
85        let mut claims: Claims = Default::default();
86        claims.registered.issuer = Some("mikkyang.com".into());
87        claims.registered.expiration = Some(1302319100);
88        let enc = claims.to_base64()?;
89        assert_eq!(claims, Claims::from_base64(&*enc)?);
90        Ok(())
91    }
92}