firebase_jwt_rs/
lib.rs

1use std::{env, error::Error};
2
3use jsonwebtoken::{decode, errors::ErrorKind, Algorithm, DecodingKey, TokenData, Validation};
4use reqwest::Url;
5use serde::{Deserialize, Serialize};
6use serde_json::{Map, Value};
7
8#[derive(Debug, Serialize, Deserialize)]
9pub struct FirebaseClaims {
10    sign_in_provider: String,
11}
12#[derive(Debug, Serialize, Deserialize)]
13pub struct Claims {
14    aud: String,
15    sub: String,
16    name: String,
17    email: String,
18    email_verified: bool,
19    user_id: String,
20    picture: String,
21    firebase: FirebaseClaims,
22    exp: u64,
23    iat: u64,
24}
25
26async fn get_pbkey() -> Result<Map<String, Value>, Box<dyn Error>> {
27    let url = format!(
28        "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com",
29    );
30    let url = Url::parse(&*url)?;
31    let res = reqwest::get(url).await?.json().await?;
32    Ok(res)
33}
34
35/// Verify Firebase client token by passing the uid and client token.
36///
37/// Prerequisites:
38/// - Please add env variable "FIREBASE_PROJECT_ID=your-firebase-project-id"
39///
40/// # Examples
41///
42/// ```
43/// use firebase_jwt_rs::*;
44///
45///
46/// async fn fetch_token() {
47///     let uid = "your-uid";
48///     let client_token = "your-client-token";
49///     // Get your uid and client token from official Firebase Client SDK:
50///     // https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients
51///     let result = verify_token(uid, client_token).await;
52///
53///     match result {
54///         Ok(res) => {
55///             let text: String = serde_json::to_string(&res.claims).unwrap();
56///             println!("result:{text}");
57///         }
58///         Err(e) => {
59///             println!("err:{e}");
60///         }
61///     }
62/// }
63/// ```
64pub async fn verify_token(
65    uid: &str,
66    client_token: &str,
67) -> Result<TokenData<Claims>, Box<dyn Error>> {
68    let res = get_pbkey().await?;
69    let ky = res.keys().nth(1).unwrap(); // we just need to pick one of the provided public key from firebase
70    let ky = res[ky].as_str().unwrap();
71    let pub_key = ky.as_bytes();
72
73    let mut validation = Validation::new(Algorithm::RS256);
74
75    let fb_project_id = env::var("FIREBASE_PROJECT_ID")?;
76    let issuer_str = format!("https://securetoken.google.com/{fb_project_id}");
77    validation.sub = Some(uid.to_string());
78    validation.set_audience(&[fb_project_id]);
79    validation.set_issuer(&[issuer_str]);
80
81    let token_data: Result<TokenData<Claims>, Box<dyn Error>> = match decode::<Claims>(
82        &client_token,
83        &DecodingKey::from_rsa_pem(pub_key).unwrap(),
84        &validation,
85    ) {
86        Ok(c) => Ok(c),
87        Err(err) => match *err.kind() {
88            ErrorKind::InvalidToken => Err("invalid-token")?,
89            ErrorKind::InvalidIssuer => Err("invalid-issuer")?,
90            ErrorKind::InvalidSubject => Err("invalid-subject")?,
91            ErrorKind::InvalidAlgorithm => Err("invalid-alg")?,
92            ErrorKind::InvalidAudience => Err("invalid-aud")?,
93            ErrorKind::InvalidEcdsaKey => Err("invalid-ecdsa-key")?,
94            ErrorKind::InvalidAlgorithmName => Err("invalid-alg-name")?,
95            ErrorKind::InvalidKeyFormat => Err("invalid-key-format")?,
96            ErrorKind::ExpiredSignature => Err("expired-signature")?,
97            ErrorKind::ImmatureSignature => Err("immature-signature")?,
98            ErrorKind::RsaFailedSigning => Err("rsa-failed-signing")?,
99            _ => Err("other-errors")?,
100        },
101    };
102
103    return token_data;
104}
105
106#[cfg(test)]
107mod tests {
108    use std::error::Error;
109
110    use jsonwebtoken::TokenData;
111
112    use crate::{verify_token, Claims};
113
114    #[tokio::test]
115    async fn e2e_fetch() {
116        let uid = "";
117        let client_token = "";
118        let result: Result<TokenData<Claims>, Box<dyn Error>> =
119            verify_token(uid, client_token).await;
120        let mut success = false;
121        match result {
122            Ok(res) => {
123                let text: String = serde_json::to_string(&res.claims).unwrap();
124                success = true;
125                println!("result:{text}");
126            }
127            Err(e) => {
128                println!("err:{e}");
129            }
130        }
131        assert_eq!(success, false)
132    }
133}