firebase_admin/auth/session_cookie/
verify.rs1use crate::auth::error::TokenVerificationError;
18use crate::auth::id_token::verifier::verify_with_key;
19use crate::auth::id_token::IdTokenClaims;
20use crate::auth::session_cookie::certs::SessionCookieCertCache;
21use crate::core::ProjectId;
22use jsonwebtoken::{decode_header, Algorithm, DecodingKey};
23
24const SESSION_COOKIE_ISSUER_PREFIX: &str = "https://session.firebase.google.com/";
25
26pub struct SessionCookieVerifier {
29 project_id: ProjectId,
30 certs: SessionCookieCertCache,
31}
32
33impl SessionCookieVerifier {
34 pub fn new(project_id: ProjectId, certs: SessionCookieCertCache) -> Self {
36 Self { project_id, certs }
37 }
38
39 pub async fn verify(&self, cookie: &str) -> Result<IdTokenClaims, TokenVerificationError> {
43 let header = decode_header(cookie)?;
44
45 if header.alg != Algorithm::RS256 {
46 return Err(TokenVerificationError::InvalidSignature);
47 }
48
49 let kid = header.kid.ok_or(TokenVerificationError::InvalidSignature)?;
50 let (n, e) = self.certs.public_key(&kid).await?;
51 let decoding_key = DecodingKey::from_rsa_components(&n, &e)
52 .map_err(|_| TokenVerificationError::InvalidSignature)?;
53
54 verify_with_key(
55 cookie,
56 &decoding_key,
57 self.project_id.as_str(),
58 SESSION_COOKIE_ISSUER_PREFIX,
59 )
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
67 use serde_json::json;
68
69 const TEST_PRIVATE_KEY_PEM: &str = include_str!("../../../tests/fixtures/test_private_key.pem");
70 const TEST_PROJECT_ID: &str = "test-project";
71
72 fn decoding_key() -> DecodingKey {
73 DecodingKey::from_rsa_pem(include_bytes!(
74 "../../../tests/fixtures/test_public_key.pem"
75 ))
76 .unwrap()
77 }
78
79 fn now() -> i64 {
80 std::time::SystemTime::now()
81 .duration_since(std::time::UNIX_EPOCH)
82 .unwrap()
83 .as_secs() as i64
84 }
85
86 fn sign(claims: &serde_json::Value) -> String {
87 let mut header = Header::new(Algorithm::RS256);
88 header.kid = Some("test-key".to_string());
89 let key = EncodingKey::from_rsa_pem(TEST_PRIVATE_KEY_PEM.as_bytes()).unwrap();
90 encode(&header, claims, &key).unwrap()
91 }
92
93 fn valid_session_cookie_claims(now: i64) -> serde_json::Value {
94 json!({
95 "iss": format!("{SESSION_COOKIE_ISSUER_PREFIX}{TEST_PROJECT_ID}"),
96 "aud": TEST_PROJECT_ID,
97 "iat": now - 10,
98 "exp": now + 3600,
99 "auth_time": now - 10,
100 "sub": "test-uid",
101 })
102 }
103
104 #[test]
105 fn accepts_a_valid_session_cookie() {
106 let token = sign(&valid_session_cookie_claims(now()));
107 let claims = verify_with_key(
108 &token,
109 &decoding_key(),
110 TEST_PROJECT_ID,
111 SESSION_COOKIE_ISSUER_PREFIX,
112 )
113 .unwrap();
114 assert_eq!(claims.sub, "test-uid");
115 }
116
117 #[test]
118 fn rejects_an_id_token_issuer_on_a_session_cookie_endpoint() {
119 let mut claims = valid_session_cookie_claims(now());
123 claims["iss"] = json!(format!("https://securetoken.google.com/{TEST_PROJECT_ID}"));
124 let token = sign(&claims);
125
126 let err = verify_with_key(
127 &token,
128 &decoding_key(),
129 TEST_PROJECT_ID,
130 SESSION_COOKIE_ISSUER_PREFIX,
131 )
132 .unwrap_err();
133 assert!(matches!(err, TokenVerificationError::IssuerMismatch));
134 }
135
136 #[test]
137 fn rejects_an_expired_session_cookie() {
138 let mut claims = valid_session_cookie_claims(now());
139 claims["exp"] = json!(now() - 100);
140 let token = sign(&claims);
141
142 let err = verify_with_key(
143 &token,
144 &decoding_key(),
145 TEST_PROJECT_ID,
146 SESSION_COOKIE_ISSUER_PREFIX,
147 )
148 .unwrap_err();
149 assert!(matches!(err, TokenVerificationError::Expired));
150 }
151}