Skip to content

Commit 600f894

Browse files
committed
Token verification for the auth emulator
1 parent 3b930f0 commit 600f894

File tree

4 files changed

+43
-14
lines changed

4 files changed

+43
-14
lines changed

firebase_admin/._auth_utils.py.swp

16 KB
Binary file not shown.

firebase_admin/_auth_client.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
"""Firebase auth client sub module."""
1616

17-
import os
1817
import time
1918

2019
import firebase_admin
@@ -27,9 +26,6 @@
2726
from firebase_admin import _user_mgt
2827
from firebase_admin import _utils
2928

30-
_EMULATOR_HOST_ENV_VAR = 'FIREBASE_AUTH_EMULATOR_HOST'
31-
_DEFAULT_AUTH_URL = 'https://identitytoolkit.googleapis.com'
32-
3329
class Client:
3430
"""Firebase Authentication client scoped to a specific tenant."""
3531

@@ -44,19 +40,17 @@ def __init__(self, app, tenant_id=None):
4440
version_header = 'Python/Admin/{0}'.format(firebase_admin.__version__)
4541
# Non-default endpoint URLs for emulator support are set in this dict later.
4642
endpoint_urls = {}
43+
self.emulated = False
4744

4845
# If an emulator is present, check that the given value matches the expected format and set
4946
# endpoint URLs to use the emulator. Additionally, use a fake credential.
50-
emulator_host = os.environ.get(_EMULATOR_HOST_ENV_VAR)
47+
emulator_host = _auth_utils.get_emulator_host()
5148
if emulator_host:
52-
if '//' in emulator_host:
53-
raise ValueError(
54-
'Invalid {0}: "{1}". It must follow format "host:port".'.format(
55-
_EMULATOR_HOST_ENV_VAR, emulator_host))
5649
base_url = 'http://{0}/identitytoolkit.googleapis.com'.format(emulator_host)
5750
endpoint_urls['v1'] = base_url + '/v1'
5851
endpoint_urls['v2beta1'] = base_url + '/v2beta1'
5952
credential = _utils.EmulatorAdminCredentials()
53+
self.emulated = True
6054
else:
6155
# Use credentials if provided
6256
credential = app.credential.get_credential()
@@ -132,7 +126,7 @@ def verify_id_token(self, id_token, check_revoked=False):
132126
raise _auth_utils.TenantIdMismatchError(
133127
'Invalid tenant ID: {0}'.format(token_tenant_id))
134128

135-
if check_revoked:
129+
if not self.emulated and check_revoked:
136130
self._check_jwt_revoked(verified_claims, _token_gen.RevokedIdTokenError, 'ID token')
137131
return verified_claims
138132

firebase_admin/_auth_utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
"""Firebase auth utils."""
1616

1717
import json
18+
import os
1819
import re
1920
from urllib import parse
2021

2122
from firebase_admin import exceptions
2223
from firebase_admin import _utils
2324

24-
25+
EMULATOR_HOST_ENV_VAR = 'FIREBASE_AUTH_EMULATOR_HOST'
2526
MAX_CLAIMS_PAYLOAD_SIZE = 1000
2627
RESERVED_CLAIMS = set([
2728
'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat',
@@ -66,6 +67,19 @@ def __iter__(self):
6667
return self
6768

6869

70+
def get_emulator_host():
71+
emulator_host = os.getenv(EMULATOR_HOST_ENV_VAR)
72+
if emulator_host and '//' in emulator_host:
73+
raise ValueError(
74+
'Invalid {0}: "{1}". It must follow format "host:port".'.format(
75+
EMULATOR_HOST_ENV_VAR, emulator_host))
76+
return emulator_host
77+
78+
79+
def is_emulated():
80+
return get_emulator_host() not in [None, '']
81+
82+
6983
def validate_uid(uid, required=False):
7084
if uid is None and not required:
7185
return None

firebase_admin/_token_gen.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from firebase_admin import _auth_utils
3232

3333

34+
#_auth_utils.is_emulated() = _auth_utils.get_emulator_host() != ''
3435
# ID token constants
3536
ID_TOKEN_ISSUER_PREFIX = 'https://securetoken.google.com/'
3637
ID_TOKEN_CERT_URI = ('https://www.googleapis.com/robot/v1/metadata/x509/'
@@ -54,6 +55,16 @@
5455
'service-accounts/default/email')
5556

5657

58+
class _EmulatedSigner(google.auth.crypt.Signer):
59+
key_id = None
60+
61+
def __init__(self):
62+
pass
63+
64+
def sign(self, message):
65+
return b''
66+
67+
5768
class _SigningProvider:
5869
"""Stores a reference to a google.auth.crypto.Signer."""
5970

@@ -78,6 +89,10 @@ def from_iam(cls, request, google_cred, service_account):
7889
signer = iam.Signer(request, google_cred, service_account)
7990
return _SigningProvider(signer, service_account)
8091

92+
@classmethod
93+
def for_emulator(cls):
94+
return _SigningProvider(_EmulatedSigner(), '[email protected]')
95+
8196

8297
class TokenGenerator:
8398
"""Generates custom tokens and session cookies."""
@@ -94,6 +109,8 @@ def __init__(self, app, http_client, url_override=None):
94109

95110
def _init_signing_provider(self):
96111
"""Initializes a signing provider by following the go/firebase-admin-sign protocol."""
112+
if _auth_utils.is_emulated():
113+
return _SigningProvider.for_emulator()
97114
# If the SDK was initialized with a service account, use it to sign bytes.
98115
google_cred = self.app.credential.get_credential()
99116
if isinstance(google_cred, google.oauth2.service_account.Credentials):
@@ -291,15 +308,15 @@ def verify(self, token, request):
291308
error_message = (
292309
'{0} expects {1}, but was given a custom '
293310
'token.'.format(self.operation, self.articled_short_name))
294-
elif not header.get('kid'):
311+
elif not _auth_utils.is_emulated() and not header.get('kid'):
295312
if header.get('alg') == 'HS256' and payload.get(
296313
'v') == 0 and 'uid' in payload.get('d', {}):
297314
error_message = (
298315
'{0} expects {1}, but was given a legacy custom '
299316
'token.'.format(self.operation, self.articled_short_name))
300317
else:
301318
error_message = 'Firebase {0} has no "kid" claim.'.format(self.short_name)
302-
elif header.get('alg') != 'RS256':
319+
elif not _auth_utils.is_emulated() and header.get('alg') != 'RS256':
303320
error_message = (
304321
'Firebase {0} has incorrect algorithm. Expected "RS256" but got '
305322
'"{1}". {2}'.format(self.short_name, header.get('alg'), verify_id_token_msg))
@@ -329,6 +346,10 @@ def verify(self, token, request):
329346
if error_message:
330347
raise self._invalid_token_error(error_message)
331348

349+
if _auth_utils.is_emulated():
350+
claims = jwt.decode(token, verify=False)
351+
claims['uid'] = claims['sub']
352+
return claims
332353
try:
333354
verified_claims = google.oauth2.id_token.verify_token(
334355
token,
@@ -342,7 +363,7 @@ def verify(self, token, request):
342363
except ValueError as error:
343364
if 'Token expired' in str(error):
344365
raise self._expired_token_error(str(error), cause=error)
345-
raise self._invalid_token_error(str(error), cause=error)
366+
raise self._invalid_token_error(str(error) + "FOO", cause=error)
346367

347368
def _decode_unverified(self, token):
348369
try:

0 commit comments

Comments
 (0)