def test_verify_exp(self, payload, leeway, error_t, error, mocktimegm): mocktimegm.return_value = self.now if error_t: with self.assertRaises(error_t) as ctx: utils.verify_exp(payload, leeway) self.assertEqual(error, str(ctx.exception)) else: self.assertIsNone(utils.verify_exp(payload, leeway))
def verify_claims(payload, issuer, audience, cid_list): """ Validates Issuer, Client IDs, Audience Issued At time and Expiration in the Payload """ verify_iss(payload, issuer) verify_cid(payload, cid_list) verify_aud(payload, audience) verify_exp(payload) verify_iat(payload)
async def authenticate_user_by_alb_auth(request): aws_alb_auth_header_name = config.get( "get_user_by_aws_alb_auth_settings.aws_alb_auth_header_name", "X-Amzn-Oidc-Data") aws_alb_claims_header_name = config.get( "get_user_by_aws_alb_auth_settings.aws_alb_claims_header_name", "X-Amzn-Oidc-Accesstoken", ) function = f"{__name__}.{sys._getframe().f_code.co_name}" log_data = {"function": function} encoded_auth_jwt = request.request.headers.get(aws_alb_auth_header_name) access_token = request.request.headers.get(aws_alb_claims_header_name) if not encoded_auth_jwt: raise Exception(f"Missing header: {aws_alb_auth_header_name}") if not access_token: raise Exception(f"Missing header: {aws_alb_claims_header_name}") jwt_headers = encoded_auth_jwt.split(".")[0] decoded_jwt_headers = base64.b64decode(jwt_headers) decoded_jwt_headers = decoded_jwt_headers.decode("utf-8") decoded_json = json.loads(decoded_jwt_headers) kid = decoded_json["kid"] # Step 2: Get the public key from regional endpoint url = "https://public-keys.auth.elb." + config.region + ".amazonaws.com/" + kid req = requests.get(url) pub_key = req.text # Step 3: Get the payload payload = jwt.decode(encoded_auth_jwt, pub_key, algorithms=["ES256"]) email = payload.get( config.get("get_user_by_aws_alb_auth_settings.jwt_email_key", "email")) if not email: raise UnableToAuthenticate("Unable to determine user from ID Token") # Step 4: Parse the Access Token # User has already passed ALB auth and successfully authenticated access_token_pub_key = None jwt_verify = config.get("get_user_by_aws_alb_auth_settings.jwt_verify", True) access_token_verify_options = {"verify_signature": jwt_verify} oidc_config = {} algorithm = None try: if jwt_verify: oidc_config = await populate_oidc_config() header = jwt.get_unverified_header(access_token) key_id = header["kid"] algorithm = header["alg"] if not algorithm: raise UnableToAuthenticate( "Access Token header does not specify a signing algorithm." ) access_token_pub_key = oidc_config["jwt_keys"][key_id] decoded_access_token = jwt.decode( access_token, access_token_pub_key, algorithms=[algorithm], options=access_token_verify_options, audience=oidc_config.get("aud"), issuer=oidc_config.get("issuer"), ) # Step 5: Verify the access token. if not jwt_verify: verify_exp(access_token) verify_iat(access_token) # Extract groups from tokens, checking both because IdPs aren't consistent here for token in [decoded_access_token, payload]: groups = token.get( config.get("get_user_by_aws_alb_auth_settings.jwt_groups_key", "groups")) if groups: break except jwt.exceptions.DecodeError as e: # This exception occurs when the access token is not JWT-parsable. It is expected with some IdPs. log.debug({ **log_data, "message": ("Unable to decode claims token. This is expected for some Identity Providers." ), "error": e, "user": email, }) log.debug(log_data, exc_info=True) groups = [] except ( ExpiredSignatureError, ImmatureSignatureError, InvalidAudienceError, InvalidIssuedAtError, InvalidIssuerError, ) as e: # JWT Validation failed, log an error and revoke the ALB auth cookie log.debug({ **log_data, "message": (str(e)), "error": e, "user": email, }) log.debug(log_data, exc_info=True) request.clear_cookie("AWSELBAuthSessionCookie-0") request.redirect(request.request.uri) groups = [] return {"user": email, "groups": groups}
def _validate_token(access_token: str) -> Mapping[str, str]: # Decoding Header & Payload from token header = jwt.get_unverified_header(access_token) payload = jwt.get_unverified_claims(access_token) # Verifying Claims try: verify_iss(payload, _TOKEN_ISSUER) except Exception: raise HTTPException(HTTPStatus.UNAUTHORIZED, "Wrong 'iss' claim.") try: verify_aud(payload, _SERVER_AUDIENCE) except Exception: raise HTTPException(HTTPStatus.UNAUTHORIZED, "Wrong 'aud' claim.") try: verify_exp(payload) except Exception: raise HTTPException(HTTPStatus.UNAUTHORIZED, "Wrong 'exp' claim.") try: verify_iat(payload) except Exception: raise HTTPException(HTTPStatus.UNAUTHORIZED, "Wrong 'iat' claim.") global _JWKS_CACHE kid = header['kid'] if kid not in _JWKS_CACHE: # retrieving key if not in cache # first metadata document try: metadata_response = requests.get(_OIDC_METADATA_DOCUMENT) # Consider any status other than 2xx an error if not metadata_response.status_code // 100 == 2: raise Exception(metadata_response.text, metadata_response.status_code) except requests.exceptions.RequestException as e: # A serious problem happened, like an SSLError or InvalidURL raise Exception("Error: {}".format(str(e))) # then key store jwks_url = metadata_response.json()["jwks_uri"] try: jwks_response = requests.get(jwks_url) # Consider any status other than 2xx an error if not jwks_response.status_code // 100 == 2: raise Exception(jwks_response.text, jwks_response.status_code) except requests.exceptions.RequestException as e: # A serious problem happened, like an SSLError or InvalidURL raise Exception("Error: {}".format(str(e))) # search for key by given 'kid' header jwks = list( filter(lambda x: x['kid'] == kid, jwks_response.json()['keys'])) if not len(jwks): raise HTTPException( HTTPStatus.UNAUTHORIZED, "Error: Could not find jwk for kid: {}".format(kid)) # put that in the cache _JWKS_CACHE[kid] = jwks[0] jwks_key = _JWKS_CACHE[kid] key = jwk.construct(jwks_key, header['alg']) message, encoded_sig = access_token.rsplit('.', 1) decoded_sig = base64url_decode(encoded_sig.encode('utf-8')) valid = key.verify(message.encode(), decoded_sig) # If the token is valid, it returns the payload if valid: return payload else: raise HTTPException(HTTPStatus.UNAUTHORIZED, "Token signature invalid.")