Skip to main content

keycloak_access/token/
decoder.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
5use serde::{Deserialize, Serialize};
6
7use crate::error::Error;
8
9#[derive(Debug, Serialize, Deserialize)]
10pub struct ResourceAccess {
11    pub account: RealmAccess,
12}
13
14#[derive(Debug, Serialize, Deserialize)]
15pub struct RealmAccess {
16    pub roles: Vec<Arc<str>>,
17}
18
19#[derive(Serialize, Deserialize, Default)]
20pub struct PartialClaims {
21    pub iss: String,
22}
23
24#[derive(Debug, Serialize, Deserialize)]
25pub struct Claims {
26    pub exp: i64,
27    pub iat: i64,
28    pub auth_time: Option<i64>,
29    pub jti: String,
30    pub iss: String,
31    pub aud: serde_json::Value,
32    pub sub: Arc<str>,
33    pub typ: String,
34    pub azp: String,
35    pub session_state: String,
36    pub acr: String,
37    #[serde(rename = "allowed-origins")]
38    pub allowed_origins: Vec<Arc<str>>,
39    pub realm_access: RealmAccess,
40    pub resource_access: ResourceAccess,
41    #[serde(default)]
42    pub scope: String,
43    #[serde(default)]
44    pub sid: String,
45    pub email_verified: bool,
46    #[serde(default)]
47    pub name: String,
48    #[serde(default)]
49    pub preferred_username: String,
50    pub given_name: String,
51    #[serde(default)]
52    pub family_name: String,
53    #[serde(default)]
54    pub email: String,
55    #[serde(skip)]
56    pub is_api_test: bool,
57}
58
59impl Default for Claims {
60    fn default() -> Self {
61        Self {
62            exp: 0,
63            iat: 0,
64            auth_time: None,
65            jti: "".to_string(),
66            iss: "".to_string(),
67            is_api_test: true,
68            sub: Arc::from("user-id"),
69            typ: "".to_string(),
70            azp: "".to_string(),
71            session_state: "".to_string(),
72            acr: "".to_string(),
73            allowed_origins: vec![],
74            realm_access: RealmAccess { roles: vec![] },
75            resource_access: ResourceAccess {
76                account: RealmAccess { roles: vec![] },
77            },
78            scope: "".to_string(),
79            sid: "".to_string(),
80            email_verified: false,
81            name: "".to_string(),
82            preferred_username: "".to_string(),
83            given_name: "".to_string(),
84            family_name: "".to_string(),
85            aud: Default::default(),
86            email: "".to_string(),
87        }
88    }
89}
90
91#[derive(Debug, Serialize, Deserialize)]
92pub struct LogoutClaims {
93    pub iat: i64,
94    pub jti: String,
95    pub iss: String,
96    pub aud: serde_json::Value,
97    pub sub: String,
98    pub typ: String,
99    pub sid: String,
100}
101
102#[derive(Clone)]
103pub struct JwtDecoder {
104    pub kid: String,
105    validation: Validation,
106    logout_validation: Validation,
107    decoding_key: DecodingKey,
108}
109
110impl JwtDecoder {
111    pub fn new(alg: Algorithm, kid: String, public_key: &str) -> Result<Self, Error> {
112        let mut validation = Validation::new(alg);
113        validation.set_audience(&["account"]);
114        let mut logout_validation = Validation::new(alg);
115        logout_validation.validate_exp = false;
116        logout_validation.required_spec_claims = HashSet::new();
117        logout_validation
118            .required_spec_claims
119            .insert("sub".to_string());
120        logout_validation
121            .required_spec_claims
122            .insert("iss".to_string());
123        logout_validation
124            .required_spec_claims
125            .insert("aud".to_string());
126        Ok(Self {
127            kid,
128            validation,
129            logout_validation,
130            decoding_key: DecodingKey::from_rsa_pem(
131                format!("-----BEGIN PUBLIC KEY-----\n{public_key}\n-----END PUBLIC KEY-----")
132                    .as_bytes(),
133            )?,
134        })
135    }
136    pub fn decode(&self, token: &str) -> Result<Claims, Error> {
137        let result =
138            decode::<Claims>(token, &self.decoding_key, &self.validation).map_err(|e| {
139                log::error!("{e:#?}");
140                e
141            })?;
142        Ok(result.claims)
143    }
144    pub fn decode_logout_token(&self, token: &str) -> Result<LogoutClaims, Error> {
145        let result = decode::<LogoutClaims>(token, &self.decoding_key, &self.logout_validation)
146            .map_err(|e| {
147                log::error!("{e:#?}");
148                e
149            })?;
150        Ok(result.claims)
151    }
152}