def _decode_unverified(self, token): try: header = jwt.decode_header(token) payload = jwt.decode(token, verify=False) return header, payload except ValueError as error: raise self._invalid_token_error(str(error), cause=error)
def receive_messages_handler(): # Verify that the request originates from the application. if (request.args.get('token', '') != current_app.config['PUBSUB_VERIFICATION_TOKEN']): return 'Invalid request', 400 # Verify that the push request originates from Cloud Pub/Sub. try: # Get the Cloud Pub/Sub-generated JWT in the "Authorization" header. bearer_token = request.headers.get('Authorization') token = bearer_token.split(' ')[1] TOKENS.append(token) header = jwt.decode_header(token) HEADERS.append(header) # Verify and decode the JWT. Underneath it checks the signature against # Google's public certs at https://www.googleapis.com/oauth2/v1/certs. # It also checks the token expiration time. claim = id_token.verify_oauth2_token(token, requests.Request()) CLAIMS.append(claim) # Check the audience field in the claim. It was specified in # `--push-auth-token-audience` when you created the subscription. assert claim['aud'] == 'example.com' except Exception as e: return 'Invalid token: {}\n'.format(e), 400 envelope = json.loads(request.data.decode('utf-8')) payload = base64.b64decode(envelope['message']['data']) MESSAGES.append(payload) # Returning any 2xx status indicates successful receipt of the message. return 'OK', 200
def verify_custom_token(custom_token, expected_claims, tenant_id=None): assert isinstance(custom_token, bytes) expected_email = MOCK_SERVICE_ACCOUNT_EMAIL if _is_emulated(): expected_email = _token_gen.AUTH_EMULATOR_EMAIL token = jwt.decode(custom_token, verify=False) else: token = google.oauth2.id_token.verify_token( custom_token, testutils.MockRequest(200, MOCK_PUBLIC_CERTS), _token_gen.FIREBASE_AUDIENCE) assert token['uid'] == MOCK_UID assert token['iss'] == expected_email assert token['sub'] == expected_email if tenant_id is None: assert 'tenant_id' not in token else: assert token['tenant_id'] == tenant_id header = jwt.decode_header(custom_token) assert header.get('typ') == 'JWT' assert header.get('alg') == 'RS256' if expected_claims: for key, value in expected_claims.items(): assert value == token['claims'][key]
def test_encode_extra_headers(signer): encoded = jwt.encode(signer, {}, header={'extra': 'value'}) header = jwt.decode_header(encoded) assert header == { 'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id, 'extra': 'value' }
def test_encode_extra_headers(signer): encoded = jwt.encode(signer, {}, header={"extra": "value"}) header = jwt.decode_header(encoded) assert header == { "typ": "JWT", "alg": "RS256", "kid": signer.key_id, "extra": "value", }
def verify_custom_token(custom_token, expected_claims): assert isinstance(custom_token, six.binary_type) token = google.oauth2.id_token.verify_token( custom_token, testutils.MockRequest(200, MOCK_PUBLIC_CERTS), FIREBASE_AUDIENCE) assert token['uid'] == MOCK_UID assert token['iss'] == MOCK_SERVICE_ACCOUNT_EMAIL assert token['sub'] == MOCK_SERVICE_ACCOUNT_EMAIL header = jwt.decode_header(custom_token) assert header.get('typ') == 'JWT' assert header.get('alg') == 'RS256' if expected_claims: for key, value in expected_claims.items(): assert value == token['claims'][key]
def _validate_iap_jwt(iap_jwt, expected_audience): try: # Retrieve public key for token signature verification. key_id = jwt.decode_header(iap_jwt).get('kid') if not key_id: return (None, None, '**ERROR: no key ID**') key = get_iap_key(key_id) # Verify token signature, expiry and audience. decoded_jwt = jwt.decode(iap_jwt, certs=key, audience=expected_audience) # Verify token issuer. if decoded_jwt.get('iss') != 'https://cloud.google.com/iap': return (None, None, '**ERROR: invalid issuer**') return (decoded_jwt['sub'], decoded_jwt['email'], '') except (ValueError, requests.exceptions.RequestException) as e: return (None, None, '**ERROR: JWT validation error {}**'.format(e))
def _get_public_key(self, iap_jwt: str) -> Optional[str]: try: key_id = jwt.decode_header(iap_jwt).get('kid') if not key_id: return None if key_id not in self.keys: # invalidate cache del self.keys # refetch if key_id not in self.keys: logging.error(f'public key {key_id} not found') return None return self.keys[key_id] except Exception: logging.exception(f'failed to retrieve public keys') return None
def _verify_token(id_token): # First check the key ID from the token's header header = jwt.decode_header(id_token) # Get the firebase app initialized in RenewalAPI.__init__ app = firebase_admin.get_app() service_acct_key_id = app.credential.signer.key_id if header['kid'] == service_acct_key_id: # This is a token signed using our Google service account's # private key. It is used primarily for custom tokens # generated for recsystems and must be verified # differently; see # renewal_backend.utils.create_custom_token claims = google.oauth2.id_token.verify_token( id_token, REQUEST, certs_url=g.client_x509_cert_url) else: claims = google.oauth2.id_token.verify_firebase_token( id_token, REQUEST, g.config.firebase.project_id) if claims.get('renewal_role') == 'recsystem': # recsystem tokens have an associated token_id used to verify whether # the token has been revoked user_id = claims.get('user_id') token_id = claims.get('renewal_token_id') if not (user_id and token_id): return None # implies unverified # Look up the recsystem _id / token_id try: filt = {'_id': ObjectId(user_id), 'token_id': token_id} found = g.db.recsystems.find_one(filt) except Exception: found = None if not found: return None return claims
def verify_id_token(self, id_token): """Verifies the signature and data for the provided JWT. Accepts a signed token string, verifies that is the current, and issued to this project, and that it was correctly signed by Google. Args: id_token: A string of the encoded JWT. Returns: dict: A dictionary of key-value pairs parsed from the decoded JWT. Raises: ValueError: The app was not initialized with a credentials.Certificate instance. AppIdenityError: The JWT was found to be invalid, the message will contain details. """ if not id_token: raise ValueError( 'Illegal ID token provided: {0}. ID token must be a non-empty ' 'string.'.format(id_token)) if isinstance(id_token, six.text_type): id_token = id_token.encode('ascii') if not isinstance(id_token, six.binary_type): raise ValueError( 'Illegal ID token provided: {0}. ID token must be a non-empty ' 'string.'.format(id_token)) try: project_id = self._app.credential.project_id if project_id is None: project_id = os.environ.get(GCLOUD_PROJECT_ENV_VAR) except AttributeError: project_id = os.environ.get(GCLOUD_PROJECT_ENV_VAR) if not project_id: raise ValueError( 'Failed to ascertain project ID from the credential or the ' 'environment. Must initialize app with a credentials.Certificate or ' 'set your Firebase project ID as the GCLOUD_PROJECT environment ' 'variable to call verify_id_token().') header = jwt.decode_header(id_token) payload = jwt.decode(id_token, verify=False) issuer = payload.get('iss') audience = payload.get('aud') subject = payload.get('sub') expected_issuer = self.ISSUER_PREFIX + project_id project_id_match_msg = ('Make sure the ID token comes from the same' ' Firebase project as the service account used' ' to authenticate this SDK.') verify_id_token_msg = ( 'See https://firebase.google.com/docs/auth/admin/verify-id-tokens' ' for details on how to retrieve an ID token.') error_message = None if not header.get('kid'): if audience == self.FIREBASE_AUDIENCE: error_message = ('verify_id_token() expects an ID token, but ' 'was given a custom token.') elif header.get('alg') == 'HS256' and payload.get( 'v') is 0 and 'uid' in payload.get('d', {}): error_message = ('verify_id_token() expects an ID token, but ' 'was given a legacy custom token.') else: error_message = 'Firebase ID token has no "kid" claim.' elif header.get('alg') != 'RS256': error_message = ('Firebase ID token has incorrect algorithm. ' 'Expected "RS256" but got "{0}". {1}'.format( header.get('alg'), verify_id_token_msg)) elif audience != project_id: error_message = ( 'Firebase ID token has incorrect "aud" (audience) claim. ' 'Expected "{0}" but got "{1}". {2} {3}'.format( project_id, audience, project_id_match_msg, verify_id_token_msg)) elif issuer != expected_issuer: error_message = ( 'Firebase ID token has incorrect "iss" (issuer) ' 'claim. Expected "{0}" but got "{1}". {2} {3}'.format( expected_issuer, issuer, project_id_match_msg, verify_id_token_msg)) elif subject is None or not isinstance(subject, six.string_types): error_message = ('Firebase ID token has no "sub" (subject) ' 'claim. ') + verify_id_token_msg elif not subject: error_message = ('Firebase ID token has an empty string "sub" ' '(subject) claim. ') + verify_id_token_msg elif len(subject) > 128: error_message = ('Firebase ID token has a "sub" (subject) ' 'claim longer than 128 ' 'characters. ') + verify_id_token_msg if error_message: raise ValueError(error_message) verified_claims = google.oauth2.id_token.verify_firebase_token( id_token, request=_request, audience=project_id) verified_claims['uid'] = verified_claims['sub'] return verified_claims
def verify(self, token, request): """Verifies the signature and data for the provided JWT.""" token = token.encode('utf-8') if isinstance(token, six.text_type) else token if not isinstance(token, six.binary_type) or not token: raise ValueError( 'Illegal {0} provided: {1}. {0} must be a non-empty ' 'string.'.format(self.short_name, token)) if not self.project_id: raise ValueError( 'Failed to ascertain project ID from the credential or the environment. Project ' 'ID is required to call {0}. Initialize the app with a credentials.Certificate ' 'or set your Firebase project ID as an app option. Alternatively set the ' 'GOOGLE_CLOUD_PROJECT environment variable.'.format( self.operation)) header = jwt.decode_header(token) payload = jwt.decode(token, verify=False) issuer = payload.get('iss') audience = payload.get('aud') subject = payload.get('sub') expected_issuer = self.issuer + self.project_id project_id_match_msg = ( 'Make sure the {0} comes from the same Firebase project as the service account used ' 'to authenticate this SDK.'.format(self.short_name)) verify_id_token_msg = ( 'See {0} for details on how to retrieve {1}.'.format( self.url, self.short_name)) error_message = None if not header.get('kid'): if audience == FIREBASE_AUDIENCE: error_message = ('{0} expects {1}, but was given a custom ' 'token.'.format(self.operation, self.articled_short_name)) elif header.get('alg') == 'HS256' and payload.get( 'v') is 0 and 'uid' in payload.get('d', {}): error_message = ( '{0} expects {1}, but was given a legacy custom ' 'token.'.format(self.operation, self.articled_short_name)) else: error_message = 'Firebase {0} has no "kid" claim.'.format( self.short_name) elif header.get('alg') != 'RS256': error_message = ( 'Firebase {0} has incorrect algorithm. Expected "RS256" but got ' '"{1}". {2}'.format(self.short_name, header.get('alg'), verify_id_token_msg)) elif audience != self.project_id: error_message = ( 'Firebase {0} has incorrect "aud" (audience) claim. Expected "{1}" but ' 'got "{2}". {3} {4}'.format(self.short_name, self.project_id, audience, project_id_match_msg, verify_id_token_msg)) elif issuer != expected_issuer: error_message = ( 'Firebase {0} has incorrect "iss" (issuer) claim. Expected "{1}" but ' 'got "{2}". {3} {4}'.format(self.short_name, expected_issuer, issuer, project_id_match_msg, verify_id_token_msg)) elif subject is None or not isinstance(subject, six.string_types): error_message = ('Firebase {0} has no "sub" (subject) claim. ' '{1}'.format(self.short_name, verify_id_token_msg)) elif not subject: error_message = ( 'Firebase {0} has an empty string "sub" (subject) claim. ' '{1}'.format(self.short_name, verify_id_token_msg)) elif len(subject) > 128: error_message = ( 'Firebase {0} has a "sub" (subject) claim longer than 128 characters. ' '{1}'.format(self.short_name, verify_id_token_msg)) if error_message: raise ValueError(error_message) verified_claims = google.oauth2.id_token.verify_token( token, request=request, audience=self.project_id, certs_url=self.cert_url) verified_claims['uid'] = verified_claims['sub'] return verified_claims
def test_encode_custom_alg_in_headers(signer): encoded = jwt.encode(signer, {}, header={"alg": "foo"}) header = jwt.decode_header(encoded) assert header == {"typ": "JWT", "alg": "foo", "kid": signer.key_id}