def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. This assertion is used during the OAuth 2.0 grant to acquire an ID token. Returns: bytes: The authorization grant assertion. """ now = _helpers.utcnow() lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) expiry = now + lifetime payload = { 'iat': _helpers.datetime_to_secs(now), 'exp': _helpers.datetime_to_secs(expiry), # The issuer must be the service account email. 'iss': self.service_account_email, # The audience must be the auth token endpoint's URI 'aud': self._token_uri, # The target audience specifies which service the ID token is # intended for. 'target_audience': self._target_audience } payload.update(self._additional_claims) token = jwt.encode(self._signer, payload) return token
def make_refresh_authorization_grant_assertion(token_info: TokenInfo, service_accout: ServiceAccount): """Create the OAuth 2.0 assertion. This assertion is used during the OAuth 2.0 grant to acquire an access token. Returns: bytes: The authorization grant assertion. """ now = _helpers.utcnow() lifetime = timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) expiry = now + lifetime payload = { 'iat': _helpers.datetime_to_secs(now), 'exp': _helpers.datetime_to_secs(expiry), # The issuer must be the service account email. 'iss': service_accout.credentials.service_account_email, # The audience must be the auth token endpoint's URI 'aud': token_info.aud, 'scope': token_info.scope, } # The subject can be a user email for domain-wide delegation. payload.setdefault('sub', token_info.uid) token = jwt.encode(service_accout.credentials._signer, payload) return token
def create_access_token(credentials_filename): credentials = service_account.Credentials.from_service_account_file(credentials_filename) seconds_since_epoch = int(time.time()) # :TODO: question = do we want to hardcode # seconds to expiry? one_hour_in_seconds = 60 * 60 payload = { 'iss': credentials.service_account_email, 'scope': 'https://www.googleapis.com/auth/datastore', 'aud': 'https://www.googleapis.com/oauth2/v4/token', 'exp': seconds_since_epoch + one_hour_in_seconds, 'iat': seconds_since_epoch, } body_template = 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=%s' body = body_template % jwt.encode(credentials.signer, payload) headers = { 'Content-Type': 'application/x-www-form-urlencoded', } response = requests.post( 'https://www.googleapis.com/oauth2/v4/token', data=body, headers=headers) if httplib.OK != response.status_code: print 'Error generating access token' sys.exit(1) access_token = response.json()['access_token'] return (access_token, credentials)
def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. This assertion is used during the OAuth 2.0 grant to acquire an access token. Returns: bytes: The authorization grant assertion. """ now = _helpers.utcnow() lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) expiry = now + lifetime payload = { 'iat': _helpers.datetime_to_secs(now), 'exp': _helpers.datetime_to_secs(expiry), # The issuer must be the service account email. 'iss': self._service_account_email, # The audience must be the auth token endpoint's URI 'aud': self._token_uri, 'scope': _helpers.scopes_to_string(self._scopes or ()) } payload.update(self._additional_claims) # The subject can be a user email for domain-wide delegation. if self._subject: payload.setdefault('sub', self._subject) token = jwt.encode(self._signer, payload) return token
def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. This assertion is used during the OAuth 2.0 grant to acquire an ID token. Returns: bytes: The authorization grant assertion. """ now = _helpers.utcnow() lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) expiry = now + lifetime payload = { "iat": _helpers.datetime_to_secs(now), "exp": _helpers.datetime_to_secs(expiry), # The issuer must be the service account email. "iss": self.service_account_email, # The audience must be the auth token endpoint's URI "aud": self._token_uri, # The target audience specifies which service the ID token is # intended for. "target_audience": self._target_audience, } payload.update(self._additional_claims) token = jwt.encode(self._signer, payload) return token
def sign_jwt(self, payload): payload = json.loads(str(payload)) encoded = jwt.encode(self.__signer, payload) if not encoded: return False return encoded.decode("utf-8")
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 generate_jwt(service_account): signer = crypt.RSASigner.from_string(service_account['private_key']) now = int(time.time()) payload = { 'iat': now, 'exp': now + 3600, 'aud': 'https://accounts.google.com/o/oauth2/token', 'iss': service_account['client_email'], 'scope': 'https://www.googleapis.com/auth/homegraph' } return jwt.encode(signer, payload)
def create_custom_token(self, uid, developer_claims=None): """Builds and signs a FirebaseCustomAuthToken. Args: uid: ID of the user for whom the token is created. developer_claims: A dictionary of claims to be included in the token. Returns: string: A token string minted from the input parameters. Raises: ValueError: If input parameters are invalid. """ if not isinstance(self._app.credential, credentials.Certificate): raise ValueError( 'Must initialize Firebase App with a certificate credential ' 'to call create_custom_token().') if developer_claims is not None: if not isinstance(developer_claims, dict): raise ValueError('developer_claims must be a dictionary') disallowed_keys = set( developer_claims.keys()) & self._RESERVED_CLAIMS_ if disallowed_keys: if len(disallowed_keys) > 1: error_message = ('Developer claims {0} are reserved and ' 'cannot be specified.'.format( ', '.join(disallowed_keys))) else: error_message = ('Developer claim {0} is reserved and ' 'cannot be specified.'.format( ', '.join(disallowed_keys))) raise ValueError(error_message) if not uid or not isinstance(uid, six.string_types) or len(uid) > 128: raise ValueError( 'uid must be a string between 1 and 128 characters.') now = int(time.time()) payload = { 'iss': self._app.credential.service_account_email, 'sub': self._app.credential.service_account_email, 'aud': self.FIREBASE_AUDIENCE, 'uid': uid, 'iat': now, 'exp': now + self.MAX_TOKEN_LIFETIME_SECONDS, } if developer_claims is not None: payload['claims'] = developer_claims return jwt.encode(self._app.credential.signer, payload)
def create_custom_token(user_id, role, token_id=None, exp=None, return_payload=False): """ Generate a custom JWT token signed with the Google service account's private key. Before this function can be used, `firebase_admin.initialize_app` must have been called. This implements the process described here: https://firebase.google.com/docs/auth/admin/create-custom-tokens#create_custom_tokens_using_a_third-party_jwt_library We do this manually, because the interface provided by `firebase_admin.auth.create_custom_token` does not allow creating tokens that don't expire. This is because firebase custom tokens are intended for use by apps to authenticate against firebase to retrieve an identity token, and are really not intended to be long-lived. However, we are using the same process to create long-lived tokens for authenticating recsystems. There's no particular harm in doing this since it's just our own JWT for use internally, but signed using our existing private key provided by the Google service account, rather than maintaining a separate key just for signing custom tokens. The other difference is we use the claim ``user_id`` instead of ``uid`` for the user ID, to be consistent with identity tokens generated by firebase. I don't know why the two token formats are inconsistent in this; it seems like a slight oversight on Google's part. """ app = firebase_admin.get_app() payload = { 'iss': app.credential.service_account_email, 'sub': app.credential.service_account_email, 'iat': int(time.time()), # google.auth's token verify requires an expiration time and always # validates it, so we create an exp that effectively never expires # before the heat death of the universe 'exp': exp or 2**64 - 1, 'user_id': user_id, # Prefix our custom claims to avoid potential future clashes 'renewal_role': role, 'renewal_token_id': token_id or secrets.token_hex(20) } token = jwt.encode(app.credential.signer, payload).decode('ascii') if return_payload: return (token, payload) else: return token
def fake_token(signer): now = calendar.timegm(datetime.datetime.utcnow().utctimetuple()) payload = { 'aud': 'example.com', 'azp': '1234567890', 'email': '*****@*****.**', 'email_verified': True, 'iat': now, 'exp': now + 3600, 'iss': 'https://accounts.google.com', 'sub': '1234567890' } header = {'alg': 'RS256', 'kid': signer.key_id, 'typ': 'JWT'} yield jwt.encode(signer, payload, header=header)
def factory(claims=None, key_id=None, use_es256_signer=False): now = _helpers.datetime_to_secs(_helpers.utcnow()) payload = { "aud": "*****@*****.**", "iat": now, "exp": now + 300, "user": "******", "metadata": { "meta": "data" }, } payload.update(claims or {}) # False is specified to remove the signer's key id for testing # headers without key ids. if key_id is False: signer._key_id = None key_id = None if use_es256_signer: return jwt.encode(es256_signer, payload, key_id=key_id) else: return jwt.encode(signer, payload, key_id=key_id)
def encode(signer, payload, header=None, key_id=None): """Make a signed JWT. Args: signer (google.auth.crypt.Signer): The signer used to sign the JWT. payload (Mapping[str, str]): The JWT payload. header (Mapping[str, str]): Additional JWT header payload. key_id (str): The key id to add to the JWT header. If the signer has a key id it will be used as the default. If this is specified it will override the signer's key id. Returns: bytes: The encoded JWT. """ return jwt.encode(signer, payload, header, key_id)
def get_id_token(payload_overrides=None, header_overrides=None): signer = crypt.RSASigner.from_string(MOCK_PRIVATE_KEY) headers = {'kid': 'mock-key-id-1'} payload = { 'aud': MOCK_CREDENTIAL.project_id, 'iss': 'https://securetoken.google.com/' + MOCK_CREDENTIAL.project_id, 'iat': int(time.time()) - 100, 'exp': int(time.time()) + 3600, 'sub': '1234567890', 'admin': True, } if header_overrides: headers = _merge_jwt_claims(headers, header_overrides) if payload_overrides: payload = _merge_jwt_claims(payload, payload_overrides) return jwt.encode(signer, payload, header=headers)
def create_custom_token(self, uid, additional_claims=None): service_account_email = self.credentials.signer_email payload = { "iss": service_account_email, "sub": service_account_email, "aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit", "uid": uid } lifetime = datetime.timedelta(minutes=60) exp = datetime.utcnow() + lifetime payload["exp"] = calendar.timegm(exp.utctimetuple()) if additional_claims: payload["claims"] = additional_claims return jwt.encode(self.credentials.signer, payload)
def fake_token(signer): now = calendar.timegm(datetime.datetime.utcnow().utctimetuple()) payload = { 'aud': 'example.com', 'azp': '1234567890', 'email': '*****@*****.**', 'email_verified': True, 'iat': now, 'exp': now + 3600, 'iss': 'https://accounts.google.com', 'sub': '1234567890' } header = { 'alg': 'RS256', 'kid': signer.key_id, 'typ': 'JWT' } yield jwt.encode(signer, payload, header=header)
def factory(claims=None, key_id=None): now = _helpers.datetime_to_secs(_helpers.utcnow()) payload = { 'aud': '*****@*****.**', 'iat': now, 'exp': now + 300, 'user': '******', 'metadata': {'meta': 'data'} } payload.update(claims or {}) # False is specified to remove the signer's key id for testing # headers without key ids. if key_id is False: signer.key_id = None key_id = None return jwt.encode(signer, payload, key_id=key_id)
def _get_signed_jwt(self): now = int(time.time()) exp = now + CRED_LIFETIME_DEFAULT_SECONDS svc_account = self._service_account payload = { # The issuer must be the service account email. CLAIM_ISSUER: svc_account, # CLAIM_SCOPE: svc_account, # The audience must be the auth token endpoint's URI CLAIM_AUDIENCE: OAUTH_TOKEN_URI, TARGET_AUDIENCE: self.target_audience, CLAIM_ISSUED_AT: now, CLAIM_EXPIRE: exp } token = jwt.encode(self._signer, payload) return token
def create_custom_token(self, uid, developer_claims=None, tenant_id=None): """Builds and signs a Firebase custom auth token.""" if developer_claims is not None: if not isinstance(developer_claims, dict): raise ValueError('developer_claims must be a dictionary') disallowed_keys = set(developer_claims.keys()) & RESERVED_CLAIMS if disallowed_keys: if len(disallowed_keys) > 1: error_message = ('Developer claims {0} are reserved and ' 'cannot be specified.'.format( ', '.join(disallowed_keys))) else: error_message = ('Developer claim {0} is reserved and ' 'cannot be specified.'.format( ', '.join(disallowed_keys))) raise ValueError(error_message) if not uid or not isinstance(uid, str) or len(uid) > 128: raise ValueError( 'uid must be a string between 1 and 128 characters.') signing_provider = self.signing_provider now = int(time.time()) payload = { 'iss': signing_provider.signer_email, 'sub': signing_provider.signer_email, 'aud': FIREBASE_AUDIENCE, 'uid': uid, 'iat': now, 'exp': now + MAX_TOKEN_LIFETIME_SECONDS, } if tenant_id: payload['tenant_id'] = tenant_id if developer_claims is not None: payload['claims'] = developer_claims header = {'alg': signing_provider.alg} try: return jwt.encode(signing_provider.signer, payload, header=header) except google.auth.exceptions.TransportError as error: msg = 'Failed to sign custom token. {0}'.format(error) raise TokenSignError(msg, error)
def test_id_token_jwt_grant(): now = _helpers.utcnow() id_token_expiry = _helpers.datetime_to_secs(now) id_token = jwt.encode(SIGNER, {'exp': id_token_expiry}).decode('utf-8') request = make_request({'id_token': id_token, 'extra': 'data'}) token, expiry, extra_data = _client.id_token_jwt_grant( request, 'http://example.com', 'assertion_value') # Check request call verify_request_params(request, { 'grant_type': _client._JWT_GRANT_TYPE, 'assertion': 'assertion_value' }) # Check result assert token == id_token # JWT does not store microseconds now = now.replace(microsecond=0) assert expiry == now assert extra_data['extra'] == 'data'
def test_id_token_jwt_grant(): now = _helpers.utcnow() id_token_expiry = _helpers.datetime_to_secs(now) id_token = jwt.encode(SIGNER, {"exp": id_token_expiry}).decode("utf-8") request = make_request({"id_token": id_token, "extra": "data"}) token, expiry, extra_data = _client.id_token_jwt_grant( request, "http://example.com", "assertion_value") # Check request call verify_request_params(request, { "grant_type": _client._JWT_GRANT_TYPE, "assertion": "assertion_value" }) # Check result assert token == id_token # JWT does not store microseconds now = now.replace(microsecond=0) assert expiry == now assert extra_data["extra"] == "data"
def _make_jwt_for_audience(self): """ Make a JSON Web Token given a service file :rtype: string :returns: signed authentication """ now = _helpers.utcnow() lifetime = timedelta(seconds=_TOKEN_EXPIRATION) self._token_expiration = now + lifetime payload = { 'aud': self._token_uri, 'iss': self._signer_email, 'iat': _helpers.datetime_to_secs(now), 'exp': _helpers.datetime_to_secs(self._token_expiration), 'scope' : self._scopes } return encode(self._signer, payload) # from google.auth.jwt.encode
def generate_jwt(claims: Any, google_application_credentials: Optional[Any] = None) -> Any: """Generates a signed JSON Web Token using a Google API Service Account.""" if os.getenv("AUTH0_JWT_TOKEN"): return os.getenv("AUTH0_JWT_TOKEN") if not google_application_credentials: google_application_credentials = os.getenv( "GOOGLE_APPLICATION_CREDENTIALS") if not google_application_credentials: raise ValueError("Please provide GOOGLE_APPLICATION_CREDENTIALS") now = int(time.time()) sa_email = Credentials.from_service_account_file( google_application_credentials).service_account_email payload = { "iat": now, "exp": now + 3600, # iss must match 'issuer' in the security configuration in your # swagger spec (e.g. service account email). It can be any string. "iss": sa_email, # aud must be either your Endpoints service name, or match the value # specified as the 'x-google-audience' in the OpenAPI document. "aud": audience, # sub and email should match the service account's email address "sub": sa_email, "email": sa_email, } claims.update(payload) signer = crypt.RSASigner.from_service_account_file( google_application_credentials) jwt_string = jwt.encode(signer, claims) return jwt_string
def generate_jwt(service_account_file: str, client_email: str) -> str: """ Generate service account JWT token :param service_account_file: Path to service account json file :param client_email: Service account email :return: JWT token """ signer = google_auth_crypt.RSASigner.from_service_account_file( service_account_file) iat = time.time() exp = iat + 3600 payload = { 'iat': iat, 'exp': exp, 'iss': client_email, 'target_audience': '563209362155-dmktm1rt2snprao3te1a5gf0tk9l39i8.apps.googleusercontent.com', 'aud': "https://www.googleapis.com/oauth2/v4/token" } jwt = google_auth_jwt.encode(signer, payload) return jwt.decode('ascii')
def generateSignedJwt(self): jwtToSign = self.generateUnsignedJwt() signedJwt = jwtGoogle.encode(self.signer, jwtToSign) return signedJwt
def _generate(self): _logger.info( "Attempting to generate access token from service account credentials in '%s'", self.credentials_filename) # # step #1 = read credentials from disk # try: assert self._credentials is None _logger.info( "attempting to read service account credentials from '%s'", self.credentials_filename) self._credentials = service_account.Credentials.from_service_account_file(self.credentials_filename) _logger.info( "successfully read service account credentials from '%s'", self.credentials_filename) except Exception as ex: _logger.error( "could not read service account credentials from '%s' - %s", self.credentials_filename, ex) self._credentials = None five_seconds = 5 self._schedule_generation(five_seconds) return # # step #2 = build access token request # seconds_since_epoch = int(time.time()) # :TODO: question = do we want to hardcode # seconds to expiry? one_hour_in_seconds = 60 * 60 payload = { 'iss': self._credentials.service_account_email, 'scope': self.scope, 'aud': 'https://www.googleapis.com/oauth2/v4/token', 'exp': seconds_since_epoch + one_hour_in_seconds, 'iat': seconds_since_epoch, } body_template = 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=%s' body = body_template % jwt.encode(self._credentials.signer, payload) headers = { 'Content-Type': 'application/x-www-form-urlencoded', } # # the if statement below is :UGLY: but required given current istio limitations # described @ https://istio.io/docs/tasks/traffic-management/egress.html # if _using_istio(): url = 'http://www.googleapis.com:443/oauth2/v4/token' else: url = 'https://www.googleapis.com/oauth2/v4/token' request = tornado.httpclient.HTTPRequest( url, method='POST', headers=tornado.httputil.HTTPHeaders(headers), body=body) http_client = tornado.httpclient.AsyncHTTPClient() http_client.fetch( request, callback=self._on_http_client_fetch_done)
def auth_header(self) -> dict: signer = RSASigner.from_service_account_file( self.service_account_auth_file) payload = {"email": self.service_account} jwt_token = jwt.encode(signer=signer, payload=payload).decode("ascii") return {"Authorization": f"Bearer {jwt_token}"}
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}
def test_encode_basic(signer): test_payload = {"test": "value"} encoded = jwt.encode(signer, test_payload) header, payload, _, _ = jwt._unverified_decode(encoded) assert payload == test_payload assert header == {"typ": "JWT", "alg": "RS256", "kid": signer.key_id}
def test_decode_bad_token_no_iat_or_exp(signer): token = jwt.encode(signer, {'test': 'value'}) with pytest.raises(ValueError) as excinfo: jwt.decode(token, PUBLIC_CERT_BYTES) assert excinfo.match(r'Token does not contain required claim')
service_account_info = json.load(fh) signer = crypt.RSASigner.from_service_account_info(service_account_info) now = int(time.time()) payload = { 'iat': now, # expires after one hour. 'exp': now + 3600, 'iss': service_account_info['client_email'], # the URL of the target service. 'target_audience': 'https://registry.endpoints.robco-166608.cloud.goog', # Google token endpoints URL, FIXME: use discovery doc 'aud': 'https://www.googleapis.com/oauth2/v4/token' } signed_jwt = jwt.encode(signer, payload) pprint(signed_jwt) # send the JWT to Google Token endpoints to request Google ID token params = { 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion': signed_jwt } headers = {"Content-Type": "application/x-www-form-urlencoded"} response = requests.post('https://www.googleapis.com/oauth2/v4/token', data=params, headers=headers) res = response.json()
def test_encode_basic(signer): test_payload = {'test': 'value'} encoded = jwt.encode(signer, test_payload) header, payload, _, _ = jwt._unverified_decode(encoded) assert payload == test_payload assert header == {'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id}