From 506b87b6e39f6e03d9adbd1bab650c4b73b5f379 Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Tue, 22 Aug 2023 23:40:59 -0400 Subject: [PATCH 1/9] feat: add clockSkewInSeconds per feedback in https://github.com/firebase/firebase-admin-python/pull/625#issuecomment-1331197410 adds unit and integration tests as well. unit tests and lint pass. --- firebase_admin/__init__.py | 12 +++++---- firebase_admin/_auth_client.py | 5 ++-- firebase_admin/_token_gen.py | 13 +++++----- firebase_admin/auth.py | 12 ++++++--- integration/test_auth.py | 47 ++++++++++++++++++++++++++++++++++ tests/test_token_gen.py | 28 ++++++++++++++++++++ 6 files changed, 100 insertions(+), 17 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index e2c8f1ec5..86a5f8dee 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -29,8 +29,8 @@ _DEFAULT_APP_NAME = '[DEFAULT]' _FIREBASE_CONFIG_ENV_VAR = 'FIREBASE_CONFIG' -_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride', 'databaseURL', 'httpTimeout', 'projectId', - 'storageBucket'] +_CONFIG_VALID_KEYS = ['clockSkewInSeconds', 'databaseAuthVariableOverride', 'databaseURL', + 'httpTimeout', 'projectId', 'storageBucket'] def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): """Initializes and returns a new App instance. @@ -49,9 +49,11 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): credential: A credential object used to initialize the SDK (optional). If none is provided, Google Application Default Credentials are used. options: A dictionary of configuration options (optional). Supported options include - ``databaseURL``, ``storageBucket``, ``projectId``, ``databaseAuthVariableOverride``, - ``serviceAccountId`` and ``httpTimeout``. If ``httpTimeout`` is not set, the SDK - uses a default timeout of 120 seconds. + ``clockSkewInSeconds``, ``databaseURL``, ``storageBucket``, ``projectId``, + ``databaseAuthVariableOverride``, ``serviceAccountId`` and ``httpTimeout``. If + ``httpTimeout`` is not set, the SDK uses a default timeout of 120 seconds. If + ``clockSkewInSeconds`` is not set, 0 is used when verifying a token or cookie. + name: Name of the app (optional). Returns: App: A newly initialized instance of App. diff --git a/firebase_admin/_auth_client.py b/firebase_admin/_auth_client.py index 0fc9d2bee..2791fb015 100644 --- a/firebase_admin/_auth_client.py +++ b/firebase_admin/_auth_client.py @@ -92,7 +92,7 @@ def create_custom_token(self, uid, developer_claims=None): return self._token_generator.create_custom_token( uid, developer_claims, tenant_id=self.tenant_id) - def verify_id_token(self, id_token, check_revoked=False): + def verify_id_token(self, id_token, check_revoked=False, clock_skew_in_seconds=0): """Verifies the signature and data for the provided JWT. Accepts a signed token string, verifies that it is current, was issued @@ -102,6 +102,7 @@ def verify_id_token(self, id_token, check_revoked=False): id_token: A string of the encoded JWT. check_revoked: Boolean, If true, checks whether the token has been revoked or the user disabled (optional). + clock_skew_in_seconds: The number of seconds to tolerate when checking the token Returns: dict: A dictionary of key-value pairs parsed from the decoded JWT. @@ -124,7 +125,7 @@ def verify_id_token(self, id_token, check_revoked=False): raise ValueError('Illegal check_revoked argument. Argument must be of type ' ' bool, but given "{0}".'.format(type(check_revoked))) - verified_claims = self._token_verifier.verify_id_token(id_token) + verified_claims = self._token_verifier.verify_id_token(id_token, clock_skew_in_seconds) if self.tenant_id: token_tenant_id = verified_claims.get('firebase', {}).get('tenant') if self.tenant_id != token_tenant_id: diff --git a/firebase_admin/_token_gen.py b/firebase_admin/_token_gen.py index 32c109d5d..5f3eea960 100644 --- a/firebase_admin/_token_gen.py +++ b/firebase_admin/_token_gen.py @@ -289,11 +289,11 @@ def __init__(self, app): invalid_token_error=InvalidSessionCookieError, expired_token_error=ExpiredSessionCookieError) - def verify_id_token(self, id_token): - return self.id_token_verifier.verify(id_token, self.request) + def verify_id_token(self, id_token, clock_skew_in_seconds=0): + return self.id_token_verifier.verify(id_token, self.request, clock_skew_in_seconds) - def verify_session_cookie(self, cookie): - return self.cookie_verifier.verify(cookie, self.request) + def verify_session_cookie(self, cookie, clock_skew_in_seconds=0): + return self.cookie_verifier.verify(cookie, self.request, clock_skew_in_seconds) class _JWTVerifier: @@ -313,7 +313,7 @@ def __init__(self, **kwargs): self._invalid_token_error = kwargs.pop('invalid_token_error') self._expired_token_error = kwargs.pop('expired_token_error') - def verify(self, token, request): + def verify(self, token, request, clock_skew_in_seconds=0): """Verifies the signature and data for the provided JWT.""" token = token.encode('utf-8') if isinstance(token, str) else token if not isinstance(token, bytes) or not token: @@ -393,7 +393,8 @@ def verify(self, token, request): token, request=request, audience=self.project_id, - certs_url=self.cert_url) + certs_url=self.cert_url, + clock_skew_in_seconds=clock_skew_in_seconds) verified_claims['uid'] = verified_claims['sub'] return verified_claims except google.auth.exceptions.TransportError as error: diff --git a/firebase_admin/auth.py b/firebase_admin/auth.py index 6902a322f..896060bb7 100644 --- a/firebase_admin/auth.py +++ b/firebase_admin/auth.py @@ -191,7 +191,7 @@ def create_custom_token(uid, developer_claims=None, app=None): return client.create_custom_token(uid, developer_claims) -def verify_id_token(id_token, app=None, check_revoked=False): +def verify_id_token(id_token, app=None, check_revoked=False, clock_skew_in_seconds=0): """Verifies the signature and data for the provided JWT. Accepts a signed token string, verifies that it is current, and issued @@ -202,6 +202,7 @@ def verify_id_token(id_token, app=None, check_revoked=False): app: An App instance (optional). check_revoked: Boolean, If true, checks whether the token has been revoked or the user disabled (optional). + clock_skew_in_seconds: The number of seconds to tolerate when checking the token. Returns: dict: A dictionary of key-value pairs parsed from the decoded JWT. @@ -217,7 +218,8 @@ def verify_id_token(id_token, app=None, check_revoked=False): record is disabled. """ client = _get_client(app) - return client.verify_id_token(id_token, check_revoked=check_revoked) + return client.verify_id_token( + id_token, check_revoked=check_revoked, clock_skew_in_seconds=clock_skew_in_seconds) def create_session_cookie(id_token, expires_in, app=None): @@ -243,7 +245,7 @@ def create_session_cookie(id_token, expires_in, app=None): return client._token_generator.create_session_cookie(id_token, expires_in) -def verify_session_cookie(session_cookie, check_revoked=False, app=None): +def verify_session_cookie(session_cookie, check_revoked=False, app=None, clock_skew_in_seconds=0): """Verifies a Firebase session cookie. Accepts a session cookie string, verifies that it is current, and issued @@ -254,6 +256,7 @@ def verify_session_cookie(session_cookie, check_revoked=False, app=None): check_revoked: Boolean, if true, checks whether the cookie has been revoked or the user disabled (optional). app: An App instance (optional). + clock_skew_in_seconds: The number of seconds to tolerate when checking the cookie Returns: dict: A dictionary of key-value pairs parsed from the decoded JWT. @@ -270,7 +273,8 @@ def verify_session_cookie(session_cookie, check_revoked=False, app=None): """ client = _get_client(app) # pylint: disable=protected-access - verified_claims = client._token_verifier.verify_session_cookie(session_cookie) + verified_claims = client._token_verifier.verify_session_cookie( + session_cookie, clock_skew_in_seconds) if check_revoked: client._check_jwt_revoked_or_disabled( verified_claims, RevokedSessionCookieError, 'session cookie') diff --git a/integration/test_auth.py b/integration/test_auth.py index 82974732d..cff88593a 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -160,6 +160,24 @@ def test_session_cookies(api_key): estimated_exp = int(time.time() + expires_in.total_seconds()) assert abs(claims['exp'] - estimated_exp) < 5 +def test_session_cookies_with_tolerance(api_key): + dev_claims = {'premium' : True, 'subscription' : 'silver'} + custom_token = auth.create_custom_token('user3', dev_claims) + id_token = _sign_in(custom_token, api_key) + expires_in = datetime.timedelta(seconds=1) + session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in) + time.sleep(2) + # expect this to fail because the cookie is expired + with pytest.raises(auth.ExpiredSessionCookieError): + auth.verify_session_cookie(session_cookie) + + # expect this to succeed because we're within the tolerance + claims = auth.verify_session_cookie(session_cookie, check_revoked=False, tolerance=2) + assert claims['uid'] == 'user3' + assert claims['premium'] is True + assert claims['subscription'] == 'silver' + assert claims['iss'].startswith('https://session.firebase.google.com') + def test_session_cookie_error(): expires_in = datetime.timedelta(days=1) with pytest.raises(auth.InvalidIdTokenError): @@ -577,6 +595,22 @@ def test_verify_id_token_revoked(new_user, api_key): claims = auth.verify_id_token(id_token, check_revoked=True) assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp +def test_verify_id_token_tolerance(new_user, api_key): + # create token that expires in 1 second. + custom_token = auth.create_custom_token(new_user.uid, expires_in=datetime.timedelta(seconds=1)) + id_token = _sign_in(custom_token, api_key) + time.sleep(1) + + # verify with tolerance of 0 seconds. This should fail. + with pytest.raises(auth.ExpiredIdTokenError) as excinfo: + auth.verify_id_token(id_token, check_revoked=False, max_age=datetime.timedelta(seconds=0)) + assert str(excinfo.value) == 'The Firebase ID token has expired.' + + # verify with tolerance of 2 seconds. This should succeed. + claims = auth.verify_id_token(id_token, check_revoked=False, max_age=datetime.timedelta(seconds=2)) + assert claims['sub'] == new_user.uid + + def test_verify_id_token_disabled(new_user, api_key): custom_token = auth.create_custom_token(new_user.uid) id_token = _sign_in(custom_token, api_key) @@ -617,6 +651,19 @@ def test_verify_session_cookie_revoked(new_user, api_key): claims = auth.verify_session_cookie(session_cookie, check_revoked=True) assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp +def test_verify_session_cookie_tolerance(new_user, api_key): + expired_session_cookie = auth.create_session_cookie(_sign_in(auth.create_custom_token(new_user.uid), api_key), expires_in=datetime.timedelta(seconds=1)) + time.sleep(1) + # Verify the session cookie with a tolerance of 0 seconds. This should + # raise an exception because the cookie is expired. + with pytest.raises(auth.InvalidSessionCookieError) as excinfo: + auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_in_seconds=0) + assert str(excinfo.value) == 'The Firebase session cookie is expired.' + + # Verify the session cookie with a tolerance of 2 seconds. This should + # not raise an exception because the cookie is within the tolerance. + auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_in_seconds=2) + def test_verify_session_cookie_disabled(new_user, api_key): custom_token = auth.create_custom_token(new_user.uid) id_token = _sign_in(custom_token, api_key) diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index 00b7956fa..239acf84d 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -554,6 +554,20 @@ def test_expired_token(self, user_mgt_app): assert 'Token expired' in str(excinfo.value) assert excinfo.value.cause is not None assert excinfo.value.http_response is None + + def test_expired_token_with_tolerance(self, user_mgt_app): + _overwrite_cert_request(user_mgt_app, MOCK_REQUEST) + id_token = self.invalid_tokens['ExpiredToken'] + if _is_emulated(): + self._assert_valid_token(id_token, user_mgt_app) + return + claims = auth.verify_id_token(id_token, app=user_mgt_app, + clock_skew_in_seconds=3700) + assert claims['admin'] is True + assert claims['uid'] == claims['sub'] + with pytest.raises(auth.ExpiredIdTokenError) as excinfo: + auth.verify_id_token(id_token, app=user_mgt_app, + clock_skew_in_seconds=3500) def test_project_id_option(self): app = firebase_admin.initialize_app( @@ -715,6 +729,20 @@ def test_expired_cookie(self, user_mgt_app): assert excinfo.value.cause is not None assert excinfo.value.http_response is None + def test_expired_cookie_with_tolerance(self, user_mgt_app): + _overwrite_cert_request(user_mgt_app, MOCK_REQUEST) + cookie = self.invalid_cookies['ExpiredCookie'] + if _is_emulated(): + self._assert_valid_cookie(cookie, user_mgt_app) + return + claims = auth.verify_session_cookie(cookie, app=user_mgt_app, check_revoked=False, + clock_skew_in_seconds=7200) + assert claims['admin'] is True + assert claims['uid'] == claims['sub'] + with pytest.raises(auth.ExpiredSessionCookieError) as excinfo: + auth.verify_session_cookie(cookie, app=user_mgt_app, check_revoked=False, + clock_skew_in_seconds=3500) + def test_project_id_option(self): app = firebase_admin.initialize_app( testutils.MockCredential(), options={'projectId': 'mock-project-id'}, name='myApp') From a503ed86d105edf65bd1d3bd5c9162aea724e262 Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Wed, 23 Aug 2023 00:19:03 -0400 Subject: [PATCH 2/9] fix: test --- integration/test_auth.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/integration/test_auth.py b/integration/test_auth.py index cff88593a..02144965b 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -164,9 +164,9 @@ def test_session_cookies_with_tolerance(api_key): dev_claims = {'premium' : True, 'subscription' : 'silver'} custom_token = auth.create_custom_token('user3', dev_claims) id_token = _sign_in(custom_token, api_key) - expires_in = datetime.timedelta(seconds=1) + expires_in = datetime.timedelta(seconds=300) session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in) - time.sleep(2) + time.sleep(300) # expect this to fail because the cookie is expired with pytest.raises(auth.ExpiredSessionCookieError): auth.verify_session_cookie(session_cookie) @@ -596,20 +596,17 @@ def test_verify_id_token_revoked(new_user, api_key): assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp def test_verify_id_token_tolerance(new_user, api_key): - # create token that expires in 1 second. - custom_token = auth.create_custom_token(new_user.uid, expires_in=datetime.timedelta(seconds=1)) - id_token = _sign_in(custom_token, api_key) + expired_id_token = _sign_in_with_password(new_user.email, 'password', api_key) time.sleep(1) + # Verify the ID token with a tolerance of 0 seconds. This should + # raise an exception because the token is expired. + with pytest.raises(auth.InvalidIdTokenError) as excinfo: + auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_in_seconds=0) + assert str(excinfo.value) == 'The Firebase ID token is expired.' - # verify with tolerance of 0 seconds. This should fail. - with pytest.raises(auth.ExpiredIdTokenError) as excinfo: - auth.verify_id_token(id_token, check_revoked=False, max_age=datetime.timedelta(seconds=0)) - assert str(excinfo.value) == 'The Firebase ID token has expired.' - - # verify with tolerance of 2 seconds. This should succeed. - claims = auth.verify_id_token(id_token, check_revoked=False, max_age=datetime.timedelta(seconds=2)) - assert claims['sub'] == new_user.uid - + # Verify the ID token with a tolerance of 2 seconds. This should + # not raise an exception because the token is within the tolerance. + auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_in_seconds=2) def test_verify_id_token_disabled(new_user, api_key): custom_token = auth.create_custom_token(new_user.uid) @@ -652,8 +649,8 @@ def test_verify_session_cookie_revoked(new_user, api_key): assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp def test_verify_session_cookie_tolerance(new_user, api_key): - expired_session_cookie = auth.create_session_cookie(_sign_in(auth.create_custom_token(new_user.uid), api_key), expires_in=datetime.timedelta(seconds=1)) - time.sleep(1) + expired_session_cookie = auth.create_session_cookie(_sign_in(auth.create_custom_token(new_user.uid), api_key), expires_in=datetime.timedelta(seconds=300)) + time.sleep(300) # Verify the session cookie with a tolerance of 0 seconds. This should # raise an exception because the cookie is expired. with pytest.raises(auth.InvalidSessionCookieError) as excinfo: From d47f82f508632a51c19cec48bd8715e73bc52c1d Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Sat, 26 Aug 2023 12:13:17 -0400 Subject: [PATCH 3/9] chore: version bump for testing --- firebase_admin/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase_admin/__about__.py b/firebase_admin/__about__.py index ff6ad252d..1008485ba 100644 --- a/firebase_admin/__about__.py +++ b/firebase_admin/__about__.py @@ -14,7 +14,7 @@ """About information (version, etc) for Firebase Admin SDK.""" -__version__ = '6.2.0' +__version__ = '6.2.1' __title__ = 'firebase_admin' __author__ = 'Firebase' __license__ = 'Apache License 2.0' From 6295b4868e2d1b1d36c0582a6d64e42dfb2079de Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Tue, 26 Sep 2023 16:50:07 -0400 Subject: [PATCH 4/9] chore: address CR --- firebase_admin/__about__.py | 2 +- firebase_admin/__init__.py | 11 ++++----- firebase_admin/_auth_client.py | 7 +++--- firebase_admin/_token_gen.py | 17 ++++++++----- firebase_admin/auth.py | 14 +++++------ integration/test_auth.py | 45 +++++++++++++++++++++++++++------- tests/test_token_gen.py | 26 ++++++++++++++------ 7 files changed, 82 insertions(+), 40 deletions(-) diff --git a/firebase_admin/__about__.py b/firebase_admin/__about__.py index 1008485ba..ff6ad252d 100644 --- a/firebase_admin/__about__.py +++ b/firebase_admin/__about__.py @@ -14,7 +14,7 @@ """About information (version, etc) for Firebase Admin SDK.""" -__version__ = '6.2.1' +__version__ = '6.2.0' __title__ = 'firebase_admin' __author__ = 'Firebase' __license__ = 'Apache License 2.0' diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index 86a5f8dee..0ca82ec5e 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -29,8 +29,8 @@ _DEFAULT_APP_NAME = '[DEFAULT]' _FIREBASE_CONFIG_ENV_VAR = 'FIREBASE_CONFIG' -_CONFIG_VALID_KEYS = ['clockSkewInSeconds', 'databaseAuthVariableOverride', 'databaseURL', - 'httpTimeout', 'projectId', 'storageBucket'] +_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride', 'databaseURL', 'httpTimeout', 'projectId', + 'storageBucket'] def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): """Initializes and returns a new App instance. @@ -49,10 +49,9 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): credential: A credential object used to initialize the SDK (optional). If none is provided, Google Application Default Credentials are used. options: A dictionary of configuration options (optional). Supported options include - ``clockSkewInSeconds``, ``databaseURL``, ``storageBucket``, ``projectId``, - ``databaseAuthVariableOverride``, ``serviceAccountId`` and ``httpTimeout``. If - ``httpTimeout`` is not set, the SDK uses a default timeout of 120 seconds. If - ``clockSkewInSeconds`` is not set, 0 is used when verifying a token or cookie. + ``databaseURL``, ``storageBucket``, ``projectId``, ``databaseAuthVariableOverride``, + ``serviceAccountId`` and ``httpTimeout``. If ``httpTimeout`` is not set, the SDK uses + a default timeout of 120 seconds. name: Name of the app (optional). Returns: diff --git a/firebase_admin/_auth_client.py b/firebase_admin/_auth_client.py index 2791fb015..38b42993a 100644 --- a/firebase_admin/_auth_client.py +++ b/firebase_admin/_auth_client.py @@ -92,7 +92,7 @@ def create_custom_token(self, uid, developer_claims=None): return self._token_generator.create_custom_token( uid, developer_claims, tenant_id=self.tenant_id) - def verify_id_token(self, id_token, check_revoked=False, clock_skew_in_seconds=0): + def verify_id_token(self, id_token, check_revoked=False, clock_skew_seconds=0): """Verifies the signature and data for the provided JWT. Accepts a signed token string, verifies that it is current, was issued @@ -102,7 +102,8 @@ def verify_id_token(self, id_token, check_revoked=False, clock_skew_in_seconds=0 id_token: A string of the encoded JWT. check_revoked: Boolean, If true, checks whether the token has been revoked or the user disabled (optional). - clock_skew_in_seconds: The number of seconds to tolerate when checking the token + clock_skew_seconds: The number of seconds to tolerate when checking the token. + Must be between 0-60. Defaults to 0. Returns: dict: A dictionary of key-value pairs parsed from the decoded JWT. @@ -125,7 +126,7 @@ def verify_id_token(self, id_token, check_revoked=False, clock_skew_in_seconds=0 raise ValueError('Illegal check_revoked argument. Argument must be of type ' ' bool, but given "{0}".'.format(type(check_revoked))) - verified_claims = self._token_verifier.verify_id_token(id_token, clock_skew_in_seconds) + verified_claims = self._token_verifier.verify_id_token(id_token, clock_skew_seconds) if self.tenant_id: token_tenant_id = verified_claims.get('firebase', {}).get('tenant') if self.tenant_id != token_tenant_id: diff --git a/firebase_admin/_token_gen.py b/firebase_admin/_token_gen.py index 5f3eea960..a2fc725e8 100644 --- a/firebase_admin/_token_gen.py +++ b/firebase_admin/_token_gen.py @@ -289,11 +289,11 @@ def __init__(self, app): invalid_token_error=InvalidSessionCookieError, expired_token_error=ExpiredSessionCookieError) - def verify_id_token(self, id_token, clock_skew_in_seconds=0): - return self.id_token_verifier.verify(id_token, self.request, clock_skew_in_seconds) + def verify_id_token(self, id_token, clock_skew_seconds=0): + return self.id_token_verifier.verify(id_token, self.request, clock_skew_seconds) - def verify_session_cookie(self, cookie, clock_skew_in_seconds=0): - return self.cookie_verifier.verify(cookie, self.request, clock_skew_in_seconds) + def verify_session_cookie(self, cookie, clock_skew_seconds=0): + return self.cookie_verifier.verify(cookie, self.request, clock_skew_seconds) class _JWTVerifier: @@ -313,7 +313,7 @@ def __init__(self, **kwargs): self._invalid_token_error = kwargs.pop('invalid_token_error') self._expired_token_error = kwargs.pop('expired_token_error') - def verify(self, token, request, clock_skew_in_seconds=0): + def verify(self, token, request, clock_skew_seconds=0): """Verifies the signature and data for the provided JWT.""" token = token.encode('utf-8') if isinstance(token, str) else token if not isinstance(token, bytes) or not token: @@ -328,6 +328,11 @@ def verify(self, token, request, clock_skew_in_seconds=0): 'or set your Firebase project ID as an app option. Alternatively set the ' 'GOOGLE_CLOUD_PROJECT environment variable.'.format(self.operation)) + if clock_skew_seconds < 0 or clock_skew_seconds > 60: + raise ValueError( + 'Illegal clock_skew_seconds value: {0}. Must be between 0 and 60, inclusive.' + .format(clock_skew_seconds)) + header, payload = self._decode_unverified(token) issuer = payload.get('iss') audience = payload.get('aud') @@ -394,7 +399,7 @@ def verify(self, token, request, clock_skew_in_seconds=0): request=request, audience=self.project_id, certs_url=self.cert_url, - clock_skew_in_seconds=clock_skew_in_seconds) + clock_skew_in_seconds=clock_skew_seconds) verified_claims['uid'] = verified_claims['sub'] return verified_claims except google.auth.exceptions.TransportError as error: diff --git a/firebase_admin/auth.py b/firebase_admin/auth.py index 896060bb7..3223f9e50 100644 --- a/firebase_admin/auth.py +++ b/firebase_admin/auth.py @@ -191,7 +191,7 @@ def create_custom_token(uid, developer_claims=None, app=None): return client.create_custom_token(uid, developer_claims) -def verify_id_token(id_token, app=None, check_revoked=False, clock_skew_in_seconds=0): +def verify_id_token(id_token, app=None, check_revoked=False, clock_skew_seconds=0): """Verifies the signature and data for the provided JWT. Accepts a signed token string, verifies that it is current, and issued @@ -202,8 +202,8 @@ def verify_id_token(id_token, app=None, check_revoked=False, clock_skew_in_secon app: An App instance (optional). check_revoked: Boolean, If true, checks whether the token has been revoked or the user disabled (optional). - clock_skew_in_seconds: The number of seconds to tolerate when checking the token. - + clock_skew_seconds: The number of seconds to tolerate when checking the token. + Must be between 0-60. Defaults to 0. Returns: dict: A dictionary of key-value pairs parsed from the decoded JWT. @@ -219,7 +219,7 @@ def verify_id_token(id_token, app=None, check_revoked=False, clock_skew_in_secon """ client = _get_client(app) return client.verify_id_token( - id_token, check_revoked=check_revoked, clock_skew_in_seconds=clock_skew_in_seconds) + id_token, check_revoked=check_revoked, clock_skew_seconds=clock_skew_seconds) def create_session_cookie(id_token, expires_in, app=None): @@ -245,7 +245,7 @@ def create_session_cookie(id_token, expires_in, app=None): return client._token_generator.create_session_cookie(id_token, expires_in) -def verify_session_cookie(session_cookie, check_revoked=False, app=None, clock_skew_in_seconds=0): +def verify_session_cookie(session_cookie, check_revoked=False, app=None, clock_skew_seconds=0): """Verifies a Firebase session cookie. Accepts a session cookie string, verifies that it is current, and issued @@ -256,7 +256,7 @@ def verify_session_cookie(session_cookie, check_revoked=False, app=None, clock_s check_revoked: Boolean, if true, checks whether the cookie has been revoked or the user disabled (optional). app: An App instance (optional). - clock_skew_in_seconds: The number of seconds to tolerate when checking the cookie + clock_skew_seconds: The number of seconds to tolerate when checking the cookie Returns: dict: A dictionary of key-value pairs parsed from the decoded JWT. @@ -274,7 +274,7 @@ def verify_session_cookie(session_cookie, check_revoked=False, app=None, clock_s client = _get_client(app) # pylint: disable=protected-access verified_claims = client._token_verifier.verify_session_cookie( - session_cookie, clock_skew_in_seconds) + session_cookie, clock_skew_seconds) if check_revoked: client._check_jwt_revoked_or_disabled( verified_claims, RevokedSessionCookieError, 'session cookie') diff --git a/integration/test_auth.py b/integration/test_auth.py index 02144965b..17f5ac7be 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -164,20 +164,25 @@ def test_session_cookies_with_tolerance(api_key): dev_claims = {'premium' : True, 'subscription' : 'silver'} custom_token = auth.create_custom_token('user3', dev_claims) id_token = _sign_in(custom_token, api_key) - expires_in = datetime.timedelta(seconds=300) + expires_in = datetime.timedelta(seconds=3) session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in) - time.sleep(300) + time.sleep(4) # expect this to fail because the cookie is expired with pytest.raises(auth.ExpiredSessionCookieError): auth.verify_session_cookie(session_cookie) # expect this to succeed because we're within the tolerance - claims = auth.verify_session_cookie(session_cookie, check_revoked=False, tolerance=2) + claims = auth.verify_session_cookie(session_cookie, check_revoked=False, clock_skew_seconds=2) assert claims['uid'] == 'user3' assert claims['premium'] is True assert claims['subscription'] == 'silver' assert claims['iss'].startswith('https://session.firebase.google.com') + with pytest.raises(ValueError): + auth.verify_session_cookie(session_cookie, clock_skew_seconds=-1) + with pytest.raises(ValueError): + auth.verify_session_cookie(session_cookie, clock_skew_seconds=61) + def test_session_cookie_error(): expires_in = datetime.timedelta(days=1) with pytest.raises(auth.InvalidIdTokenError): @@ -601,12 +606,12 @@ def test_verify_id_token_tolerance(new_user, api_key): # Verify the ID token with a tolerance of 0 seconds. This should # raise an exception because the token is expired. with pytest.raises(auth.InvalidIdTokenError) as excinfo: - auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_in_seconds=0) + auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_seconds=0) assert str(excinfo.value) == 'The Firebase ID token is expired.' # Verify the ID token with a tolerance of 2 seconds. This should # not raise an exception because the token is within the tolerance. - auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_in_seconds=2) + auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_seconds=2) def test_verify_id_token_disabled(new_user, api_key): custom_token = auth.create_custom_token(new_user.uid) @@ -649,17 +654,39 @@ def test_verify_session_cookie_revoked(new_user, api_key): assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp def test_verify_session_cookie_tolerance(new_user, api_key): - expired_session_cookie = auth.create_session_cookie(_sign_in(auth.create_custom_token(new_user.uid), api_key), expires_in=datetime.timedelta(seconds=300)) - time.sleep(300) + expired_session_cookie = auth.create_session_cookie( + _sign_in(auth.create_custom_token(new_user.uid), api_key), + expires_in=datetime.timedelta(seconds=3) + ) + time.sleep(3) # Verify the session cookie with a tolerance of 0 seconds. This should # raise an exception because the cookie is expired. with pytest.raises(auth.InvalidSessionCookieError) as excinfo: - auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_in_seconds=0) + auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=0) assert str(excinfo.value) == 'The Firebase session cookie is expired.' # Verify the session cookie with a tolerance of 2 seconds. This should # not raise an exception because the cookie is within the tolerance. - auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_in_seconds=2) + auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=2) + +def test_verify_session_cookie_clock_skew_seconds_range(new_user, api_key): + expired_session_cookie = auth.create_session_cookie( + _sign_in(auth.create_custom_token(new_user.uid), api_key), + expires_in=datetime.timedelta(seconds=3) + ) + # Verify the session cookie with a tolerance of 0 seconds. This should + # raise an exception because the cookie is expired. + with pytest.raises(ValueError) as excinfo: + auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=-1) + assert str(excinfo.value) == 'clock_skew_seconds must be between 0 and 60.' + with pytest.raises(ValueError) as excinfo: + auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=61) + assert str(excinfo.value) == 'clock_skew_seconds must be between 0 and 60.' + + # Verify the session cookie with a tolerance of 2 seconds. This should + # not raise an exception because the cookie is within the tolerance. + auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=2) + def test_verify_session_cookie_disabled(new_user, api_key): custom_token = auth.create_custom_token(new_user.uid) diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index 239acf84d..2e619836d 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -440,6 +440,10 @@ class TestVerifyIdToken: 'iat': int(time.time()) - 10000, 'exp': int(time.time()) - 3600 }), + 'ExpiredTokenShort': _get_id_token({ + 'iat': int(time.time()) - 10000, + 'exp': int(time.time()) - 30 + }), 'BadFormatToken': 'foobar' } @@ -447,7 +451,8 @@ class TestVerifyIdToken: 'NoKid', 'WrongKid', 'FutureToken', - 'ExpiredToken' + 'ExpiredToken', + 'ExpiredTokenShort', ] def _assert_valid_token(self, id_token, app): @@ -557,17 +562,17 @@ def test_expired_token(self, user_mgt_app): def test_expired_token_with_tolerance(self, user_mgt_app): _overwrite_cert_request(user_mgt_app, MOCK_REQUEST) - id_token = self.invalid_tokens['ExpiredToken'] + id_token = self.invalid_tokens['ExpiredTokenShort'] if _is_emulated(): self._assert_valid_token(id_token, user_mgt_app) return claims = auth.verify_id_token(id_token, app=user_mgt_app, - clock_skew_in_seconds=3700) + clock_skew_seconds=60) assert claims['admin'] is True assert claims['uid'] == claims['sub'] with pytest.raises(auth.ExpiredIdTokenError) as excinfo: auth.verify_id_token(id_token, app=user_mgt_app, - clock_skew_in_seconds=3500) + clock_skew_seconds=20) def test_project_id_option(self): app = firebase_admin.initialize_app( @@ -633,6 +638,10 @@ class TestVerifySessionCookie: 'iat': int(time.time()) - 10000, 'exp': int(time.time()) - 3600 }), + 'ExpiredCookieShort': _get_session_cookie({ + 'iat': int(time.time()) - 10000, + 'exp': int(time.time()) - 30 + }), 'BadFormatCookie': 'foobar', 'IDToken': TEST_ID_TOKEN, } @@ -641,7 +650,8 @@ class TestVerifySessionCookie: 'NoKid', 'WrongKid', 'FutureCookie', - 'ExpiredCookie' + 'ExpiredCookie', + 'ExpiredCookieShort', ] def _assert_valid_cookie(self, cookie, app, check_revoked=False): @@ -731,17 +741,17 @@ def test_expired_cookie(self, user_mgt_app): def test_expired_cookie_with_tolerance(self, user_mgt_app): _overwrite_cert_request(user_mgt_app, MOCK_REQUEST) - cookie = self.invalid_cookies['ExpiredCookie'] + cookie = self.invalid_cookies['ExpiredCookieShort'] if _is_emulated(): self._assert_valid_cookie(cookie, user_mgt_app) return claims = auth.verify_session_cookie(cookie, app=user_mgt_app, check_revoked=False, - clock_skew_in_seconds=7200) + clock_skew_seconds=59) assert claims['admin'] is True assert claims['uid'] == claims['sub'] with pytest.raises(auth.ExpiredSessionCookieError) as excinfo: auth.verify_session_cookie(cookie, app=user_mgt_app, check_revoked=False, - clock_skew_in_seconds=3500) + clock_skew_seconds=29) def test_project_id_option(self): app = firebase_admin.initialize_app( From 7ae857eca056c17e0f1c3a058daec3310b7895ce Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Tue, 26 Sep 2023 17:51:41 -0400 Subject: [PATCH 5/9] fix:lint --- integration/test_auth.py | 15 +++++++++------ tests/test_token_gen.py | 10 +++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/integration/test_auth.py b/integration/test_auth.py index 17f5ac7be..16cfa822c 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -655,14 +655,15 @@ def test_verify_session_cookie_revoked(new_user, api_key): def test_verify_session_cookie_tolerance(new_user, api_key): expired_session_cookie = auth.create_session_cookie( - _sign_in(auth.create_custom_token(new_user.uid), api_key), + _sign_in(auth.create_custom_token(new_user.uid), api_key), expires_in=datetime.timedelta(seconds=3) ) time.sleep(3) # Verify the session cookie with a tolerance of 0 seconds. This should # raise an exception because the cookie is expired. with pytest.raises(auth.InvalidSessionCookieError) as excinfo: - auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=0) + auth.verify_session_cookie(expired_session_cookie, check_revoked=False, + clock_skew_seconds=0) assert str(excinfo.value) == 'The Firebase session cookie is expired.' # Verify the session cookie with a tolerance of 2 seconds. This should @@ -671,22 +672,24 @@ def test_verify_session_cookie_tolerance(new_user, api_key): def test_verify_session_cookie_clock_skew_seconds_range(new_user, api_key): expired_session_cookie = auth.create_session_cookie( - _sign_in(auth.create_custom_token(new_user.uid), api_key), + _sign_in(auth.create_custom_token(new_user.uid), api_key), expires_in=datetime.timedelta(seconds=3) ) # Verify the session cookie with a tolerance of 0 seconds. This should # raise an exception because the cookie is expired. with pytest.raises(ValueError) as excinfo: - auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=-1) + auth.verify_session_cookie( + expired_session_cookie, check_revoked=False, clock_skew_seconds=-1) assert str(excinfo.value) == 'clock_skew_seconds must be between 0 and 60.' with pytest.raises(ValueError) as excinfo: - auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=61) + auth.verify_session_cookie( + expired_session_cookie, check_revoked=False, clock_skew_seconds=61) assert str(excinfo.value) == 'clock_skew_seconds must be between 0 and 60.' # Verify the session cookie with a tolerance of 2 seconds. This should # not raise an exception because the cookie is within the tolerance. auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=2) - + def test_verify_session_cookie_disabled(new_user, api_key): custom_token = auth.create_custom_token(new_user.uid) diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index 2e619836d..64540f26f 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -559,19 +559,19 @@ def test_expired_token(self, user_mgt_app): assert 'Token expired' in str(excinfo.value) assert excinfo.value.cause is not None assert excinfo.value.http_response is None - + def test_expired_token_with_tolerance(self, user_mgt_app): _overwrite_cert_request(user_mgt_app, MOCK_REQUEST) id_token = self.invalid_tokens['ExpiredTokenShort'] if _is_emulated(): self._assert_valid_token(id_token, user_mgt_app) return - claims = auth.verify_id_token(id_token, app=user_mgt_app, + claims = auth.verify_id_token(id_token, app=user_mgt_app, clock_skew_seconds=60) assert claims['admin'] is True assert claims['uid'] == claims['sub'] - with pytest.raises(auth.ExpiredIdTokenError) as excinfo: - auth.verify_id_token(id_token, app=user_mgt_app, + with pytest.raises(auth.ExpiredIdTokenError): + auth.verify_id_token(id_token, app=user_mgt_app, clock_skew_seconds=20) def test_project_id_option(self): @@ -749,7 +749,7 @@ def test_expired_cookie_with_tolerance(self, user_mgt_app): clock_skew_seconds=59) assert claims['admin'] is True assert claims['uid'] == claims['sub'] - with pytest.raises(auth.ExpiredSessionCookieError) as excinfo: + with pytest.raises(auth.ExpiredSessionCookieError): auth.verify_session_cookie(cookie, app=user_mgt_app, check_revoked=False, clock_skew_seconds=29) From 9148a3f56d57b36001db5708d12e921dcafbef6f Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Wed, 27 Sep 2023 15:14:05 -0400 Subject: [PATCH 6/9] chore: address CR --- integration/test_auth.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/test_auth.py b/integration/test_auth.py index 16cfa822c..3bfa889ec 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -601,11 +601,11 @@ def test_verify_id_token_revoked(new_user, api_key): assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp def test_verify_id_token_tolerance(new_user, api_key): - expired_id_token = _sign_in_with_password(new_user.email, 'password', api_key) + expired_id_token = _sign_in_with_password(new_user_with_params()) time.sleep(1) # Verify the ID token with a tolerance of 0 seconds. This should # raise an exception because the token is expired. - with pytest.raises(auth.InvalidIdTokenError) as excinfo: + with pytest.raises(auth.ExpiredIdTokenError) as excinfo: auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_seconds=0) assert str(excinfo.value) == 'The Firebase ID token is expired.' @@ -661,7 +661,7 @@ def test_verify_session_cookie_tolerance(new_user, api_key): time.sleep(3) # Verify the session cookie with a tolerance of 0 seconds. This should # raise an exception because the cookie is expired. - with pytest.raises(auth.InvalidSessionCookieError) as excinfo: + with pytest.raises(auth.ExpiredSessionCookieError) as excinfo: auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=0) assert str(excinfo.value) == 'The Firebase session cookie is expired.' From 96f030d6b7f724e6b3c80a11759d59fd2a101d44 Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Thu, 12 Oct 2023 12:26:27 -0400 Subject: [PATCH 7/9] chore: remove test --- integration/test_auth.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/integration/test_auth.py b/integration/test_auth.py index 3bfa889ec..e5a16d47a 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -600,19 +600,6 @@ def test_verify_id_token_revoked(new_user, api_key): claims = auth.verify_id_token(id_token, check_revoked=True) assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp -def test_verify_id_token_tolerance(new_user, api_key): - expired_id_token = _sign_in_with_password(new_user_with_params()) - time.sleep(1) - # Verify the ID token with a tolerance of 0 seconds. This should - # raise an exception because the token is expired. - with pytest.raises(auth.ExpiredIdTokenError) as excinfo: - auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_seconds=0) - assert str(excinfo.value) == 'The Firebase ID token is expired.' - - # Verify the ID token with a tolerance of 2 seconds. This should - # not raise an exception because the token is within the tolerance. - auth.verify_id_token(expired_id_token, check_revoked=False, clock_skew_seconds=2) - def test_verify_id_token_disabled(new_user, api_key): custom_token = auth.create_custom_token(new_user.uid) id_token = _sign_in(custom_token, api_key) From 46c3ed095228613b4766480c7b1557f547c2f2d8 Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Thu, 12 Oct 2023 14:10:05 -0400 Subject: [PATCH 8/9] fix: remove more tests --- integration/test_auth.py | 60 ---------------------------------------- 1 file changed, 60 deletions(-) diff --git a/integration/test_auth.py b/integration/test_auth.py index e5a16d47a..e1d01a254 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -160,29 +160,6 @@ def test_session_cookies(api_key): estimated_exp = int(time.time() + expires_in.total_seconds()) assert abs(claims['exp'] - estimated_exp) < 5 -def test_session_cookies_with_tolerance(api_key): - dev_claims = {'premium' : True, 'subscription' : 'silver'} - custom_token = auth.create_custom_token('user3', dev_claims) - id_token = _sign_in(custom_token, api_key) - expires_in = datetime.timedelta(seconds=3) - session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in) - time.sleep(4) - # expect this to fail because the cookie is expired - with pytest.raises(auth.ExpiredSessionCookieError): - auth.verify_session_cookie(session_cookie) - - # expect this to succeed because we're within the tolerance - claims = auth.verify_session_cookie(session_cookie, check_revoked=False, clock_skew_seconds=2) - assert claims['uid'] == 'user3' - assert claims['premium'] is True - assert claims['subscription'] == 'silver' - assert claims['iss'].startswith('https://session.firebase.google.com') - - with pytest.raises(ValueError): - auth.verify_session_cookie(session_cookie, clock_skew_seconds=-1) - with pytest.raises(ValueError): - auth.verify_session_cookie(session_cookie, clock_skew_seconds=61) - def test_session_cookie_error(): expires_in = datetime.timedelta(days=1) with pytest.raises(auth.InvalidIdTokenError): @@ -640,43 +617,6 @@ def test_verify_session_cookie_revoked(new_user, api_key): claims = auth.verify_session_cookie(session_cookie, check_revoked=True) assert claims['iat'] * 1000 >= user.tokens_valid_after_timestamp -def test_verify_session_cookie_tolerance(new_user, api_key): - expired_session_cookie = auth.create_session_cookie( - _sign_in(auth.create_custom_token(new_user.uid), api_key), - expires_in=datetime.timedelta(seconds=3) - ) - time.sleep(3) - # Verify the session cookie with a tolerance of 0 seconds. This should - # raise an exception because the cookie is expired. - with pytest.raises(auth.ExpiredSessionCookieError) as excinfo: - auth.verify_session_cookie(expired_session_cookie, check_revoked=False, - clock_skew_seconds=0) - assert str(excinfo.value) == 'The Firebase session cookie is expired.' - - # Verify the session cookie with a tolerance of 2 seconds. This should - # not raise an exception because the cookie is within the tolerance. - auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=2) - -def test_verify_session_cookie_clock_skew_seconds_range(new_user, api_key): - expired_session_cookie = auth.create_session_cookie( - _sign_in(auth.create_custom_token(new_user.uid), api_key), - expires_in=datetime.timedelta(seconds=3) - ) - # Verify the session cookie with a tolerance of 0 seconds. This should - # raise an exception because the cookie is expired. - with pytest.raises(ValueError) as excinfo: - auth.verify_session_cookie( - expired_session_cookie, check_revoked=False, clock_skew_seconds=-1) - assert str(excinfo.value) == 'clock_skew_seconds must be between 0 and 60.' - with pytest.raises(ValueError) as excinfo: - auth.verify_session_cookie( - expired_session_cookie, check_revoked=False, clock_skew_seconds=61) - assert str(excinfo.value) == 'clock_skew_seconds must be between 0 and 60.' - - # Verify the session cookie with a tolerance of 2 seconds. This should - # not raise an exception because the cookie is within the tolerance. - auth.verify_session_cookie(expired_session_cookie, check_revoked=False, clock_skew_seconds=2) - def test_verify_session_cookie_disabled(new_user, api_key): custom_token = auth.create_custom_token(new_user.uid) From 04eca6ce80bec2638a66de8af220d9e1ad6ab209 Mon Sep 17 00:00:00 2001 From: Chris Hua Date: Thu, 26 Oct 2023 09:35:44 -0400 Subject: [PATCH 9/9] chore: address CR --- firebase_admin/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase_admin/auth.py b/firebase_admin/auth.py index 3223f9e50..84873c3da 100644 --- a/firebase_admin/auth.py +++ b/firebase_admin/auth.py @@ -256,7 +256,7 @@ def verify_session_cookie(session_cookie, check_revoked=False, app=None, clock_s check_revoked: Boolean, if true, checks whether the cookie has been revoked or the user disabled (optional). app: An App instance (optional). - clock_skew_seconds: The number of seconds to tolerate when checking the cookie + clock_skew_seconds: The number of seconds to tolerate when checking the cookie. Returns: dict: A dictionary of key-value pairs parsed from the decoded JWT.