10000 New error type for ID token and session cookie verification · rinlevan/firebase-admin-python@d1b2161 · GitHub
[go: up one dir, main page]

Skip to content

Commit d1b2161

Browse files
committed
New error type for ID token and session cookie verification
1 parent bbf9c0f commit d1b2161

File tree

4 files changed

+173
-79
lines changed

4 files changed

+173
-79
lines changed

firebase_admin/_auth_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ class InvalidIdTokenError(exceptions.InvalidArgumentError):
216216

217217
default_message = 'The provided ID token is invalid'
218218

219-
def __init__(self, message, cause, http_response=None):
219+
def __init__(self, message, cause=None, http_response=None):
220220
exceptions.InvalidArgumentError.__init__(self, message, cause, http_response)
221221

222222

firebase_admin/_token_gen.py

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,18 @@ def __init__(self, app):
217217
project_id=app.project_id, short_name='ID token',
218218
operation='verify_id_token()',
219219
doc_url='https://firebase.google.com/docs/auth/admin/verify-id-tokens',
220-
cert_url=ID_TOKEN_CERT_URI, issuer=ID_TOKEN_ISSUER_PREFIX)
220+
cert_url=ID_TOKEN_CERT_URI,
221+
issuer=ID_TOKEN_ISSUER_PREFIX,
222+
invalid_token_error=_auth_utils.InvalidIdTokenError,
223+
expired_token_error=ExpiredIdTokenError)
221224
self.cookie_verifier = _JWTVerifier(
222225
project_id=app.project_id, short_name='session cookie',
223226
operation='verify_session_cookie()',
224227
doc_url='https://firebase.google.com/docs/auth/admin/verify-id-tokens',
225-
cert_url=COOKIE_CERT_URI, issuer=COOKIE_ISSUER_PREFIX)
228+
cert_url=COOKIE_CERT_URI,
229+
issuer=COOKIE_ISSUER_PREFIX,
230+
invalid_token_error=InvalidSessionCookieError,
231+
expired_token_error=ExpiredSessionCookieError)
226232

227233
def verify_id_token(self, id_token):
228234
return self.id_token_verifier.verify(id_token, self.request)
@@ -245,6 +251,8 @@ def __init__(self, **kwargs):
245251
self.articled_short_name = 'an {0}'.format(self.short_name)
246252
else:
247253
self.articled_short_name = 'a {0}'.format(self.short_name)
254+
self.invalid_token_error = kwargs.pop('invalid_token_error')
255+
self.expired_token_error = kwargs.pop('expired_token_error')
248256

249257
def verify(self, token, request):
250258
"""Verifies the signature and data for the provided JWT."""
@@ -261,8 +269,7 @@ def verify(self, token, request):
261269
'or set your Firebase project ID as an app option. Alternatively set the '
262270
'GOOGLE_CLOUD_PROJECT environment variable.'.format(self.operation))
263271

264-
header = jwt.decode_header(token)
265-
payload = jwt.decode(token, verify=False)
272+
header, payload = self._decode_unverified(token)
266273
issuer = payload.get('iss')
267274
audience = payload.get('aud')
268275
subject = payload.get('sub')
@@ -275,12 +282,12 @@ def verify(self, token, request):
275282
'See {0} for details on how to retrieve {1}.'.format(self.url, self.short_name))
276283

277284
error_message = None
278-
if not header.get('kid'):
279-
if audience == FIREBASE_AUDIENCE:
280-
error_message = (
281-
'{0} expects {1}, but was given a custom '
282-
'token.'.format(self.operation, self.articled_short_name))
283-
elif header.get('alg') == 'HS256' and payload.get(
285+
if audience == FIREBASE_AUDIENCE:
286+
error_message = (
287+
'{0} expects {1}, but was given a custom '
288+
'token.'.format(self.operation, self.articled_short_name))
289+
elif not header.get('kid'):
290+
if header.get('alg') == 'HS256' and payload.get(
284291
'v') is 0 and 'uid' in payload.get('d', {}):
285292
error_message = (
286293
'{0} expects {1}, but was given a legacy custom '
@@ -315,19 +322,64 @@ def verify(self, token, request):
315322
'{1}'.format(self.short_name, verify_id_token_msg))
316323

317324
if error_message:
318-
raise ValueError(error_message)
325+
raise self.invalid_token_error(error_message)
319326

320-
verified_claims = google.oauth2.id_token.verify_token(
321-
token,
322-
request=request,
323-
audience=self.project_id,
324-
certs_url=self.cert_url)
325-
verified_claims['uid'] = verified_claims['sub']
326-
return verified_claims
327+
try:
328+
verified_claims = google.oauth2.id_token.verify_token(
329+
token,
330+
request=request,
331+
audience=self.project_id,
332+
certs_url=self.cert_url)
333+
verified_claims['uid'] = verified_claims['sub']
334+
return verified_claims
335+
except google.auth.exceptions.TransportError as error:
336+
msg = 'Failed to fetch requried public key certificates: {0}'.format(error)
337+
raise CertificateFetchError(msg, error)
338+
except ValueError as error:
339+
message = str(error)
340+
if 'Token expired' in message:
341+
raise self.expired_token_error(message, error)
342+
raise self.invalid_token_error('Invalid ID token: {0}'.format(message), error)
343+
344+
def _decode_unverified(self, token):
345+
try:
346+
header = jwt.decode_header(token)
347+
payload = jwt.decode(token, verify=False)
348+
return header, payload
349+
except ValueError as error:
350+
raise self.invalid_token_error(str(error), error)
327351

328352

329353
class TokenSignError(exceptions.UnknownError):
330354
"""Unexpected error while signing a Firebase custom token."""
331355

332356
def __init__(self, message, cause):
333357
exceptions.UnknownError.__init__(self, message, cause)
358+
359+
360+
class CertificateFetchError(exceptions.UnknownError):
361+
"""Error while retrieving public key certificates required to verify a token."""
362+
363+
def __init__(self, message, cause):
364+
exceptions.UnknownError.__init__(self, message, cause)
365+
366+
367+
class ExpiredIdTokenError(_auth_utils.InvalidIdTokenError):
368+
"""The provided Firebae ID token is expired."""
369+
370+
def __init__(self, message, cause):
371+
_auth_utils.InvalidIdTokenError.__init__(self, message, cause)
372+
373+
374+
class InvalidSessionCookieError(exceptions.InvalidArgumentError):
375+
"""The provided string is not a valid Firebase session cookie."""
376+
377+
def __init__(self, message, cause=None):
378+
exceptions.InvalidArgumentError.__init__(self, message, cause)
379+
380+
381+
class ExpiredSessionCookieError(InvalidSessionCookieError):
382+
"""The provided Firebase session cookie is expired."""
383+
384+
def __init__(self, message, cause):
385+
InvalidSessionCookieError.__init__(self, message, cause)

firebase_admin/auth.py

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,10 @@
3131

3232

3333
_AUTH_ATTRIBUTE = '_auth'
34-
_ID_TOKEN_REVOKED = 'ID_TOKEN_REVOKED'
35-
_SESSION_COOKIE_REVOKED = 'SESSION_COOKIE_REVOKED'
3634

3735

3836
__all__ = [
3937
'ActionCodeSettings',
40-
'AuthError',
4138
'DELETE_ATTRIBUTE',
4239
'ErrorInfo',
4340
'ExportedUserRecord',
@@ -76,17 +73,21 @@
7673
]
7774

7875
ActionCodeSettings = _user_mgt.ActionCodeSettings
76+
CertificateFetcherror = _token_gen.CertificateFetchError
7977
DELETE_ATTRIBUTE = _user_mgt.DELETE_ATTRIBUTE
8078
ErrorInfo = _user_import.ErrorInfo
79+
ExpiredIdTokenError = _token_gen.ExpiredIdTokenError
80+
ExpiredSessionCookieError = _token_gen.ExpiredSessionCookieError
8181
ExportedUserRecord = _user_mgt.ExportedUserRecord
82-
ListUsersPage = _user_mgt.ListUsersPage
83-
UserImportHash = _user_import.UserImportHash
8482
ImportUserRecord = _user_import.ImportUserRecord
8583
InvalidDynamicLinkDomainError = _auth_utils.InvalidDynamicLinkDomainError
8684
InvalidIdTokenError = _auth_utils.InvalidIdTokenError
85+
InvalidSessionCookieError = _token_gen.InvalidSessionCookieError
86+
ListUsersPage = _user_mgt.ListUsersPage
8787
TokenSignError = _token_gen.TokenSignError
8888
UidAlreadyExistsError = _auth_utils.UidAlreadyExistsError
8989
UnexpectedResponseError = _auth_utils.UnexpectedResponseError
90+
UserImportHash = _user_import.UserImportHash
9091
UserImportResult = _user_import.UserImportResult
9192
UserInfo = _user_mgt.UserInfo
9293
UserMetadata = _user_mgt.UserMetadata
@@ -143,15 +144,18 @@ def verify_id_token(id_token, app=None, check_revoked=False):
143144
Args:
144145
id_token: A string of the encoded JWT.
145146
app: An App instance (optional).
146-
check_revoked: Boolean, If true, checks whether the token has been revoked (optional).
147+
check_revoked: Boolean, if true, checks whether the token has been revoked (optional).
147148
148149
Returns:
149150
dict: A dictionary of key-value pairs parsed from the decoded JWT.
150151
151152
Raises:
152-
ValueError: If the JWT was found to be invalid, or if the App's project ID cannot
153-
be determined.
154-
AuthError: If ``check_revoked`` is requested and the token was revoked.
153+
ValueError: If ``id_token`` is not a string, empty or ``check_revoked`` is not a boolean.
154+
InvalidIdTokenError: If the given ID token is not a valid Firebase ID token, or if
155+
``check_revoked`` is ``True`` and the ID token has been revoked.
156+
ExpiredIdTokenError: If the given ID token is expired.
157+
CertificateFetchError: If an error occurs while fetching the public key certificates
158+
required to verify the ID token.
155159
"""
156160
if not isinstance(check_revoked, bool):
157161
# guard against accidental wrong assignment.
@@ -160,7 +164,7 @@ def verify_id_token(id_token, app=None, check_revoked=False):
160164
token_verifier = _get_auth_service(app).token_verifier
161165
verified_claims = token_verifier.verify_id_token(id_token)
162166
if check_revoked:
163-
_check_jwt_revoked(verified_claims, _ID_TOKEN_REVOKED, 'ID token', app)
167+
_check_jwt_revoked(verified_claims, 'ID token', InvalidIdTokenError, app)
164168
return verified_claims
165169

166170

@@ -201,14 +205,18 @@ def verify_session_cookie(session_cookie, check_revoked=False, app=None):
201205
dict: A dictionary of key-value pairs parsed from the decoded JWT.
202206
203207
Raises:
204-
ValueError: If the cookie was found to be invalid, or if the App's project ID cannot
205-
be determined.
206-
AuthError: If ``check_revoked`` is requested and the cookie was revoked.
208+
ValueError: If ``session_cookie`` is not a string, empty or ``check_revoked`` is not
209+
a boolean.
210+
InvalidSessionCookieError: If the given session cookie is not a valid Firebase session
211+
cookie, or if``check_revoked`` is ``True`` and the cookie has been revoked.
212+
ExpiredSessionCookieError: If the given session cookie is expired.
213+
CertificateFetchError: If an error occurs while fetching the public key certificates
214+
required to verify the cookie.
207215
"""
208216
token_verifier = _get_auth_service(app).token_verifier
209217
verified_claims = token_verifier.verify_session_cookie(session_cookie)
210218
if check_revoked:
211-
_check_jwt_revoked(verified_claims, _SESSION_COOKIE_REVOKED, 'session cookie', app)
219+
_check_jwt_revoked(verified_claims, 'session cookie', InvalidSessionCookieError, app)
212220
return verified_claims
213221

214222

@@ -513,19 +521,10 @@ def generate_sign_in_with_email_link(email, action_code_settings, app=None):
513521
'EMAIL_SIGNIN', email, action_code_settings=action_code_settings)
514522

515523

516-
def _check_jwt_revoked(verified_claims, error_code, label, app):
524+
def _check_jwt_revoked(verified_claims, label, exc_type, app):
517525
user = get_user(verified_claims.get('uid'), app=app)
518526
if verified_claims.get('iat') * 1000 < user.tokens_valid_after_timestamp:
519-
raise AuthError(error_code, 'The Firebase {0} has been revoked.'.format(label))
520-
521-
522-
class AuthError(Exception):
523-
"""Represents an Exception encountered while invoking the Firebase auth API."""
524-
525-
def __init__(self, code, message, error=None):
526-
Exception.__init__(self, message)
527-
self.code = code
528-
self.detail = error
527+
raise exc_type('The Firebase {0} has been revoked.'.format(label))
529528

530529

531530
class _AuthService(object):

0 commit comments

Comments
 (0)
0