rs_firebase_admin_sdk/auth/token/
mod.rs1#[cfg(test)]
2mod test;
3
4pub mod cache;
5pub mod crypto;
6pub mod error;
7pub mod jwt;
8
9use cache::KeyCache;
10use crypto::JwtRsaPubKey;
11use error::TokenVerificationError;
12use error_stack::{Report, ResultExt};
13use jwt::{JWTAlgorithm, JWToken};
14use std::future::Future;
15use time::{Duration, OffsetDateTime};
16
17const GOOGLE_ID_TOKEN_ISSUER_PREFIX: &str = "https://securetoken.google.com/";
18const GOOGLE_COOKIE_ISSUER_PREFIX: &str = "https://session.firebase.google.com/";
19
20#[cfg(feature = "tokens")]
21pub(crate) const GOOGLE_PUB_KEY_URI: &str =
22 "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
23#[cfg(feature = "tokens")]
24pub(crate) const GOOGLE_COOKIE_PUB_KEY_URI: &str =
25 "https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys";
26
27pub trait TokenVerifier: Sized + Sync + Send {
28 fn verify_token(
29 &self,
30 id_token: &str,
31 ) -> impl Future<Output = Result<JWToken, Report<TokenVerificationError>>> + Send;
32}
33
34pub struct EmulatedTokenVerifier {
35 _project_id: String,
36 _issuer: String,
37}
38
39impl EmulatedTokenVerifier {
40 pub fn new(project_id: String) -> Self {
41 Self {
42 _project_id: project_id.clone(),
43 _issuer: project_id,
44 }
45 }
46}
47
48impl TokenVerifier for EmulatedTokenVerifier {
49 async fn verify_token(
50 &self,
51 id_token: &str,
52 ) -> Result<JWToken, Report<TokenVerificationError>> {
53 let token = JWToken::from_encoded(id_token)
54 .change_context(TokenVerificationError::FailedParsing)?;
55
56 Ok(token)
59 }
60}
61
62pub struct LiveTokenVerifier<CacheT: KeyCache> {
63 project_id: String,
64 issuer: String,
65 key_cache: CacheT,
66}
67
68impl<CacheT: KeyCache + Send + Sync> TokenVerifier for LiveTokenVerifier<CacheT> {
69 async fn verify_token(
70 &self,
71 id_token: &str,
72 ) -> Result<JWToken, Report<TokenVerificationError>> {
73 let token = JWToken::from_encoded(id_token)
74 .change_context(TokenVerificationError::FailedParsing)?;
75
76 self.verify(&token).await?;
77
78 Ok(token)
79 }
80}
81
82impl<CacheT: KeyCache + Send + Sync> LiveTokenVerifier<CacheT> {
83 pub fn new_id_verifier(
85 project_id: String,
86 key_cache: CacheT,
87 ) -> Result<Self, Report<TokenVerificationError>> {
88 Ok(Self {
89 issuer: String::new() + GOOGLE_ID_TOKEN_ISSUER_PREFIX + &project_id,
90 project_id,
91 key_cache,
92 })
93 }
94
95 pub fn new_cookie_verifier(
97 project_id: String,
98 key_cache: CacheT,
99 ) -> Result<Self, Report<TokenVerificationError>> {
100 Ok(Self {
101 issuer: String::new() + GOOGLE_COOKIE_ISSUER_PREFIX + &project_id,
102 project_id,
103 key_cache,
104 })
105 }
106
107 async fn verify_signature(
108 &self,
109 token: &JWToken,
110 ) -> Result<(), Report<TokenVerificationError>> {
111 let keys = self
112 .key_cache
113 .get_keys()
114 .await
115 .change_context(TokenVerificationError::FailedGettingKeys)?;
116
117 let key_id = token
118 .header
119 .kid
120 .as_ref()
121 .ok_or(TokenVerificationError::FailedGettingKeys)?;
122
123 let key = keys
124 .get(key_id)
125 .ok_or(Report::new(TokenVerificationError::InvalidSignatureKey))?;
126
127 let is_valid = key
128 .verify(token.payload.as_bytes(), &token.signature)
129 .change_context(TokenVerificationError::InvalidSignature)?;
130
131 if !is_valid {
132 return Err(Report::new(TokenVerificationError::InvalidSignature));
133 }
134
135 Ok(())
136 }
137
138 fn verify_header(&self, token: &JWToken) -> Result<(), Report<TokenVerificationError>> {
139 match token.header.alg {
140 JWTAlgorithm::RS256 => Ok(()),
141 _ => Err(Report::new(
142 TokenVerificationError::InvalidSignatureAlgorithm,
143 )),
144 }
145 }
146
147 fn verify_claims(&self, token: &JWToken) -> Result<(), Report<TokenVerificationError>> {
148 let now = OffsetDateTime::now_utc();
149
150 if token.critical_claims.exp <= now {
151 return Err(Report::new(TokenVerificationError::Expired));
152 }
153
154 if token.critical_claims.iat > now + Duration::seconds(10) {
156 return Err(Report::new(TokenVerificationError::IssuedInFuture));
157 }
158
159 if token.critical_claims.auth_time > now {
160 return Err(Report::new(TokenVerificationError::IssuedInFuture));
161 }
162
163 if token.critical_claims.aud != self.project_id {
164 return Err(Report::new(TokenVerificationError::InvalidAudience));
165 }
166
167 if token.critical_claims.iss != self.issuer {
168 return Err(Report::new(TokenVerificationError::InvalidIssuer));
169 }
170
171 if token.critical_claims.sub.is_empty() {
172 return Err(Report::new(TokenVerificationError::MissingSubject));
173 }
174
175 Ok(())
176 }
177
178 pub async fn verify(&self, token: &JWToken) -> Result<(), Report<TokenVerificationError>> {
180 self.verify_header(token)?;
181 self.verify_claims(token)?;
182 self.verify_signature(token).await
183 }
184}