firebase_admin_sdk/auth/
verifier.rs1use crate::auth::keys::{KeyFetchError, PublicKeyManager};
2use jsonwebtoken::{decode, decode_header, Algorithm, Validation};
3use serde::{Deserialize, Serialize};
4use std::time::{SystemTime, UNIX_EPOCH};
5use thiserror::Error;
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 iss: String,
22 pub aud: String,
23 pub sub: String,
24 pub iat: u64,
25 pub exp: u64,
26 pub auth_time: u64,
27 pub user_id: String,
28 pub provider_id: Option<String>,
29 pub name: Option<String>,
30 pub picture: Option<String>,
31 pub email: Option<String>,
32 pub email_verified: Option<bool>,
33
34 #[serde(flatten)]
35 pub claims: serde_json::Map<String, serde_json::Value>,
36}
37
38pub struct IdTokenVerifier {
39 project_id: String,
40 key_manager: PublicKeyManager,
41}
42
43impl IdTokenVerifier {
44 pub fn new(project_id: String) -> Self {
45 Self {
46 project_id,
47 key_manager: PublicKeyManager::new(),
48 }
49 }
50
51 pub async fn verify_id_token(
53 &self,
54 token: &str,
55 ) -> Result<FirebaseTokenClaims, TokenVerificationError> {
56 self.verify_token_with_issuer(
57 token,
58 &format!("https://securetoken.google.com/{}", self.project_id),
59 )
60 .await
61 }
62
63 pub async fn verify_session_cookie(
65 &self,
66 token: &str,
67 ) -> Result<FirebaseTokenClaims, TokenVerificationError> {
68 self.verify_token_with_issuer(
69 token,
70 &format!("https://session.firebase.google.com/{}", self.project_id),
71 )
72 .await
73 }
74
75 async fn verify_token_with_issuer(
76 &self,
77 token: &str,
78 issuer: &str,
79 ) -> Result<FirebaseTokenClaims, TokenVerificationError> {
80 let header = decode_header(token)?;
82 let kid = header.kid.ok_or_else(|| {
83 TokenVerificationError::InvalidToken("Missing kid in header".to_string())
84 })?;
85
86 let key = self.key_manager.get_key(&kid).await?;
88
89 let mut validation = Validation::new(Algorithm::RS256);
91 validation.set_audience(&[&self.project_id]);
92 validation.set_issuer(&[issuer]);
93
94 let token_data = decode::<FirebaseTokenClaims>(token, &key, &validation)?;
96 let claims = token_data.claims;
97
98 if claims.sub.is_empty() {
100 return Err(TokenVerificationError::InvalidToken(
101 "Subject (sub) claim must not be empty".to_string(),
102 ));
103 }
104
105 let now = SystemTime::now()
106 .duration_since(UNIX_EPOCH)
107 .unwrap()
108 .as_secs() as usize;
109 if claims.auth_time > (now + 300) as u64 {
112 return Err(TokenVerificationError::InvalidToken(
114 "Auth time is in the future".to_string(),
115 ));
116 }
117
118 Ok(claims)
119 }
120}