firebase_admin_sdk/auth/
verifier.rs1use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
2use serde::{Deserialize, Serialize};
3use crate::auth::keys::{PublicKeyManager, KeyFetchError};
4use thiserror::Error;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7#[derive(Error, Debug)]
8pub enum TokenVerificationError {
9 #[error("Key fetch error: {0}")]
10 KeyFetchError(#[from] KeyFetchError),
11 #[error("JWT validation error: {0}")]
12 JwtError(#[from] jsonwebtoken::errors::Error),
13 #[error("Invalid token: {0}")]
14 InvalidToken(String),
15 #[error("Token expired")]
16 Expired,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct FirebaseTokenClaims {
21 pub aud: String,
22 pub iss: String,
23 pub sub: String,
24 pub exp: usize,
25 pub iat: usize,
26 pub auth_time: usize,
27 pub user_id: String,
28 #[serde(flatten)]
29 pub claims: serde_json::Map<String, serde_json::Value>,
30}
31
32pub struct IdTokenVerifier {
33 project_id: String,
34 key_manager: PublicKeyManager,
35}
36
37impl IdTokenVerifier {
38 pub fn new(project_id: String) -> Self {
39 Self {
40 project_id,
41 key_manager: PublicKeyManager::new(),
42 }
43 }
44
45 pub async fn verify_id_token(&self, token: &str) -> Result<FirebaseTokenClaims, TokenVerificationError> {
47 self.verify_token_with_issuer(token, &format!("https://securetoken.google.com/{}", self.project_id)).await
48 }
49
50 pub async fn verify_session_cookie(&self, token: &str) -> Result<FirebaseTokenClaims, TokenVerificationError> {
52 self.verify_token_with_issuer(token, &format!("https://session.firebase.google.com/{}", self.project_id)).await
53 }
54
55 async fn verify_token_with_issuer(&self, token: &str, issuer: &str) -> Result<FirebaseTokenClaims, TokenVerificationError> {
56 let header = decode_header(token)?;
58 let kid = header.kid.ok_or_else(|| TokenVerificationError::InvalidToken("Missing kid in header".to_string()))?;
59
60 let public_key_pem = self.key_manager.get_key(&kid).await?;
62 let key = DecodingKey::from_rsa_pem(public_key_pem.as_bytes())?;
63
64 let mut validation = Validation::new(Algorithm::RS256);
66 validation.set_audience(&[&self.project_id]);
67 validation.set_issuer(&[issuer]);
68
69 let token_data = decode::<FirebaseTokenClaims>(token, &key, &validation)?;
71 let claims = token_data.claims;
72
73 if claims.sub.is_empty() {
75 return Err(TokenVerificationError::InvalidToken("Subject (sub) claim must not be empty".to_string()));
76 }
77
78 let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as usize;
79 if claims.auth_time > now + 300 { return Err(TokenVerificationError::InvalidToken("Auth time is in the future".to_string()));
83 }
84
85 Ok(claims)
86 }
87}