async def _parse_id_token(self, token: Token, nonce: str) -> UserInfo: """Return an instance of UserInfo from token's ``id_token``. Args: token: the token given by the ``token_endpoint``. Must include an ``id_token`` field. nonce: the nonce value originally sent in the initial authorization request. This value should match the one inside the token. Returns: An object representing the user. """ metadata = await self.load_metadata() claims_params = { "nonce": nonce, "client_id": self._client_auth.client_id, } if "access_token" in token: # If we got an `access_token`, there should be an `at_hash` claim # in the `id_token` that we can check against. claims_params["access_token"] = token["access_token"] claims_cls = CodeIDToken else: claims_cls = ImplicitIDToken alg_values = metadata.get("id_token_signing_alg_values_supported", ["RS256"]) jwt = JsonWebToken(alg_values) claim_options = {"iss": {"values": [metadata["issuer"]]}} # Try to decode the keys in cache first, then retry by forcing the keys # to be reloaded jwk_set = await self.load_jwks() try: claims = jwt.decode( token["id_token"], key=jwk_set, claims_cls=claims_cls, claims_options=claim_options, claims_params=claims_params, ) except ValueError: logger.info("Reloading JWKS after decode error") jwk_set = await self.load_jwks(force=True ) # try reloading the jwks claims = jwt.decode( token["id_token"], key=jwk_set, claims_cls=claims_cls, claims_options=claim_options, claims_params=claims_params, ) claims.validate(leeway=120) # allows 2 min of clock skew return UserInfo(claims)
class JWTTokenFactory(TokenFactory): __slots__ = ('_now', '_secret', '_algorithm') def __init__(self, now: Callable[[], datetime], secret: str) -> None: self._now = now self._secret = secret self._jwt = JsonWebToken(['HS256']) async def generate(self, user_id: UserId, expiration_in_days: int) -> str: now = self._now() return self._jwt.encode( header={'alg': 'HS256'}, payload={ 'iss': 'Authlib', 'sub': dumps({'id': user_id.value()}).decode('utf-8'), 'iat': now, 'exp': now + timedelta(days=expiration_in_days), }, key=self._secret, ).decode('utf-8') async def decode(self, token: str) -> Dict[str, Any]: token = token.split('Bearer ')[-1] claims = self._jwt.decode(s=token, key=self._secret) return { 'iss': claims.get('iss'), 'sub': claims.get('sub'), 'iat': claims.get('iat'), 'exp': claims.get('exp'), } async def read(self, token: str) -> UserId: payload = await self.decode(token) data = loads(payload['sub']) return UserId(data['id'])
def validate_claims(self, id_token, params): jwt = JsonWebToken() claims = jwt.decode(id_token, 'secret', claims_cls=HybridIDToken, claims_params=params) claims.validate()
def test_authorize_token(self): self.prepare_data() rv = self.client.post('/oauth/authorize', data={ 'response_type': 'code', 'client_id': 'code-client', 'state': 'bar', 'scope': 'openid profile', 'redirect_uri': 'https://a.b', 'user_id': '1' }) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) self.assertEqual(params['state'], 'bar') code = params['code'] headers = self.create_basic_header('code-client', 'code-secret') rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'redirect_uri': 'https://a.b', 'code': code, }, headers=headers) resp = json.loads(rv.data) self.assertIn('access_token', resp) self.assertIn('id_token', resp) jwt = JsonWebToken() claims = jwt.decode( resp['id_token'], 'secret', claims_cls=CodeIDToken, claims_options={'iss': {'value': 'Authlib'}} ) claims.validate()
def validate_claims(self, id_token, params): jwt = JsonWebToken(['HS256']) claims = jwt.decode( id_token, 'secret', claims_cls=ImplicitIDToken, claims_params=params ) claims.validate()
async def parse_id_token(self, token, nonce, claims_options=None): """Return an instance of UserInfo from token's ``id_token``.""" claims_params = dict( nonce=nonce, client_id=self.client_id, ) if 'access_token' in token: claims_params['access_token'] = token['access_token'] claims_cls = CodeIDToken else: claims_cls = ImplicitIDToken metadata = await self.load_server_metadata() if claims_options is None and 'issuer' in metadata: claims_options = {'iss': {'values': [metadata['issuer']]}} alg_values = metadata.get('id_token_signing_alg_values_supported') if not alg_values: alg_values = ['RS256'] jwt = JsonWebToken(alg_values) jwk_set = await self.fetch_jwk_set() try: claims = jwt.decode( token['id_token'], key=JsonWebKey.import_key_set(jwk_set), claims_cls=claims_cls, claims_options=claims_options, claims_params=claims_params, ) except ValueError: jwk_set = await self.fetch_jwk_set(force=True) claims = jwt.decode( token['id_token'], key=JsonWebKey.import_key_set(jwk_set), claims_cls=claims_cls, claims_options=claims_options, claims_params=claims_params, ) # https://github.com/lepture/authlib/issues/259 if claims.get('nonce_supported') is False: claims.params['nonce'] = None claims.validate(leeway=120) return UserInfo(claims)
def decode_token(self, token, key): jwt = JsonWebToken(['RS256']) try: claims = jwt.decode(token, key) claims.validate() return claims except JoseError as e: raise GraphExecutionError( f'Unable to decode token: {e.error}', code=401, )
def extract_software_statement(self, software_statement, request): key = self.resolve_public_key(request) if not key: raise UnapprovedSoftwareStatementError() try: jwt = JsonWebToken(self.software_statement_alg_values_supported) claims = jwt.decode(software_statement, key) # there is no need to validate claims return claims except JoseError: raise InvalidSoftwareStatementError()
def parse_id_token(self, token, nonce, claims_options=None, leeway=120): """Return an instance of UserInfo from token's ``id_token``.""" if "id_token" not in token: return None def load_key(header, _): alg = header.get("alg") if alg in ["HS256", "HS384", "HS512"]: # For HS256: client secret is used for id_token signing return self.client_secret elif alg in ["RS256", "RS384", "RS512"]: jwk_set = JsonWebKey.import_key_set(self.fetch_jwk_set()) try: return jwk_set.find_by_kid(header.get("kid")) except ValueError: # re-try with new jwk set jwk_set = JsonWebKey.import_key_set( self.fetch_jwk_set(force=True)) return jwk_set.find_by_kid(header.get("kid")) else: raise RuntimeError(f"Unsupported id_token algorithm: '{alg}'") claims_params = dict(nonce=nonce, client_id=self.client_id) if "access_token" in token: claims_params["access_token"] = token["access_token"] claims_cls = CodeIDToken else: claims_cls = ImplicitIDToken if claims_options is None and "issuer" in self.metadata: claims_options = {"iss": {"values": [self.metadata["issuer"]]}} alg_values = self.metadata.get("id_token_signing_alg_values_supported") if alg_values: _jwt = JsonWebToken(alg_values) else: _jwt = jwt claims = _jwt.decode( token["id_token"], key=load_key, claims_cls=claims_cls, claims_options=claims_options, claims_params=claims_params, ) # https://github.com/lepture/authlib/issues/259 if claims.get("nonce_supported") is False: claims.params["nonce"] = None claims.validate(leeway=leeway) return UserInfo(claims)
def _parse_access_token(provider, oauth_token): token = oauth_token.get("access_token") if token is None: return {} def load_key(header, payload): jwk_set = JsonWebKey.import_key_set(provider.fetch_jwk_set(force=True)) return jwk_set.find_by_kid(header.get("kid")) metadata = provider.load_server_metadata() algs = metadata.get("id_token_signing_alg_values_supported", ["RS256"]) jwt = JsonWebToken(algs) claims = {"exp": {"essential": True}} return jwt.decode(token, key=load_key, claims_options=claims)
def parse_id_token(self, token, nonce, claims_options=None, leeway=120): """Return an instance of UserInfo from token's ``id_token``.""" if 'id_token' not in token: return None def load_key(header, _): jwk_set = JsonWebKey.import_key_set(self.fetch_jwk_set()) try: return jwk_set.find_by_kid(header.get('kid')) except ValueError: # re-try with new jwk set jwk_set = JsonWebKey.import_key_set( self.fetch_jwk_set(force=True)) return jwk_set.find_by_kid(header.get('kid')) claims_params = dict( nonce=nonce, client_id=self.client_id, ) if 'access_token' in token: claims_params['access_token'] = token['access_token'] claims_cls = CodeIDToken else: claims_cls = ImplicitIDToken metadata = self.load_server_metadata() if claims_options is None and 'issuer' in metadata: claims_options = {'iss': {'values': [metadata['issuer']]}} alg_values = metadata.get('id_token_signing_alg_values_supported') if alg_values: _jwt = JsonWebToken(alg_values) else: _jwt = jwt claims = _jwt.decode( token['id_token'], key=load_key, claims_cls=claims_cls, claims_options=claims_options, claims_params=claims_params, ) # https://github.com/lepture/authlib/issues/259 if claims.get('nonce_supported') is False: claims.params['nonce'] = None claims.validate(leeway=leeway) return UserInfo(claims)
def auth(): print(request) token = oauth.local.authorize_access_token() print(token) print(token['id_token']) from authlib.jose import JsonWebToken, JWTClaims jwt = JsonWebToken(['HS256']) claims = jwt.decode(token['id_token'], key='') # print(claims) # print(claims.validate()) user = oauth.local.get('/userinfo').json() session['user'] = user return redirect('/')
def _parse_id_token(self, request, token, claims_options=None, leeway=120): """Return an instance of UserInfo from token's ``id_token``.""" if 'id_token' not in token: return None def load_key(header, payload): jwk_set = JsonWebKey.import_key_set(self.fetch_jwk_set()) try: return jwk_set.find_by_kid(header.get('kid')) except ValueError: # re-try with new jwk set jwk_set = JsonWebKey.import_key_set( self.fetch_jwk_set(force=True)) return jwk_set.find_by_kid(header.get('kid')) nonce = self.framework.get_session_data(request, 'nonce') claims_params = dict( nonce=nonce, client_id=self.client_id, ) if 'access_token' in token: claims_params['access_token'] = token['access_token'] claims_cls = CodeIDToken else: claims_cls = ImplicitIDToken metadata = self.load_server_metadata() if claims_options is None and 'issuer' in metadata: claims_options = {'iss': {'values': [metadata['issuer']]}} alg_values = metadata.get('id_token_signing_alg_values_supported') if not alg_values: alg_values = ['RS256'] jwt = JsonWebToken(alg_values) claims = jwt.decode( token['id_token'], key=load_key, claims_cls=claims_cls, claims_options=claims_options, claims_params=claims_params, ) claims.validate(leeway=leeway) return UserInfo(claims)
def decorated(*args, **kwargs): token = get_token_from_header() jwks = requests.get( "https://{}/.well-known/jwks.json".format( current_app.config["AUTH0_DOMAIN"] ) ).json() def load_key(header, payload): kid = header['kid'] for key in jwks["keys"]: if key["kid"] == kid: return key raise AuthError( { "code": "invalid_header", "description": "Unable to find appropriate key" }, 401 ) jwt = JsonWebToken(current_app.config['ALGORITHMS']) try: payload = jwt.decode( token, load_key, claims_options={ "iss": { "essential": True, "value": "https://{}/".format( current_app.config["AUTH0_DOMAIN"] ) }, "aud": { "essential": True, "value": current_app.config["API_AUDIENCE"] } } ) except ( MissingClaimError, InvalidClaimError, ExpiredTokenError, InvalidTokenError ) as e: raise AuthError( {"code": e.error, "description": e.description}, 401 ) except Exception: raise AuthError( { "code": "invalid_header", "description": "Unable to decode authentication token." }, 401 ) if ( not permissions or (set(permissions) & set(payload.get('permissions', []))) ): _request_ctx_stack.top.current_user = payload return func(*args, **kwargs) raise AuthError( { "code": "missing_permission", "description": "missing {} permission".format( ', '.join(set(permissions)) ) }, 401 )
async def _do_jwt_login( self, login_submission: JsonDict, should_issue_refresh_token: bool = False) -> LoginResponse: token = login_submission.get("token", None) if token is None: raise LoginError(403, "Token field for JWT is missing", errcode=Codes.FORBIDDEN) from authlib.jose import JsonWebToken, JWTClaims from authlib.jose.errors import BadSignatureError, InvalidClaimError, JoseError jwt = JsonWebToken([self.jwt_algorithm]) claim_options = {} if self.jwt_issuer is not None: claim_options["iss"] = { "value": self.jwt_issuer, "essential": True } if self.jwt_audiences is not None: claim_options["aud"] = { "values": self.jwt_audiences, "essential": True } try: claims = jwt.decode( token, key=self.jwt_secret, claims_cls=JWTClaims, claims_options=claim_options, ) except BadSignatureError: # We handle this case separately to provide a better error message raise LoginError( 403, "JWT validation failed: Signature verification failed", errcode=Codes.FORBIDDEN, ) except JoseError as e: # A JWT error occurred, return some info back to the client. raise LoginError( 403, "JWT validation failed: %s" % (str(e), ), errcode=Codes.FORBIDDEN, ) try: claims.validate(leeway=120) # allows 2 min of clock skew # Enforce the old behavior which is rolled out in productive # servers: if the JWT contains an 'aud' claim but none is # configured, the login attempt will fail if claims.get("aud") is not None: if self.jwt_audiences is None or len(self.jwt_audiences) == 0: raise InvalidClaimError("aud") except JoseError as e: raise LoginError( 403, "JWT validation failed: %s" % (str(e), ), errcode=Codes.FORBIDDEN, ) user = claims.get(self.jwt_subject_claim, None) if user is None: raise LoginError(403, "Invalid JWT", errcode=Codes.FORBIDDEN) user_id = UserID(user, self.hs.hostname).to_string() result = await self._complete_login( user_id, login_submission, create_non_existent_users=True, should_issue_refresh_token=should_issue_refresh_token, ) return result