Example #1
0
 def validate_claims(self, id_token, params):
     jwt = JsonWebToken()
     claims = jwt.decode(id_token,
                         'secret',
                         claims_cls=HybridIDToken,
                         claims_params=params)
     claims.validate()
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'])
Example #3
0
    def parse_access_token(self, token, claims_options=None, leeway=120):
        """Decode and validate a Cognito access token and return its payload.

        Note: this function is based on authlib.DjangoRemoteApp._parse_id_token
        to make use of the same server settings and key cache. The token claims
        are AWS Cognito specific.

        Args:
          token (str): access token (base64 encoded JWT)

        Returns:
          claims (dict): the token payload

        Raises:
          authlib.jose.errors.JoseError: if token is invalid
          ValueError: if the key id is not present in the jwks.json
        """

        # this is a copy from the _parse_id_token equivalent function
        def load_key(header, payload):
            jwk_set = self.fetch_jwk_set()
            try:
                return jwk.loads(jwk_set, header.get("kid"))
            except ValueError:
                # re-try with new jwk set
                jwk_set = self.fetch_jwk_set(force=True)
                return jwk.loads(jwk_set, header.get("kid"))

        metadata = self.load_server_metadata()
        claims_options = {
            "aud": {
                "essential": True,
                "value": settings.NENS_AUTH_RESOURCE_SERVER_ID
            },
            "iss": {
                "essential": True,
                "value": metadata["issuer"]
            },
            "sub": {
                "essential": True
            },
            "scope": {
                "essential": True
            },
            **(claims_options or {}),
        }

        alg_values = metadata.get("id_token_signing_alg_values_supported")
        if not alg_values:
            alg_values = ["RS256"]

        claims = JsonWebToken(alg_values).decode(token,
                                                 key=load_key,
                                                 claims_options=claims_options)

        # Preprocess the token (to add the "aud" claim)
        preprocess_access_token(claims)

        claims.validate(leeway=leeway)
        return claims
Example #4
0
    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()
Example #5
0
    def test_init_algorithms(self):
        _jwt = JsonWebToken(['RS256'])
        self.assertRaises(UnsupportedAlgorithmError, _jwt.encode,
                          {'alg': 'HS256'}, {}, 'k')

        _jwt = JsonWebToken('RS256')
        self.assertRaises(UnsupportedAlgorithmError, _jwt.encode,
                          {'alg': 'HS256'}, {}, 'k')
 def validate_claims(self, id_token, params):
     jwt = JsonWebToken(['HS256'])
     claims = jwt.decode(
         id_token, 'secret',
         claims_cls=ImplicitIDToken,
         claims_params=params
     )
     claims.validate()
Example #7
0
    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)
Example #8
0
 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,
         )
Example #9
0
def _jwt_encode(alg, payload, key):
    jwt = JsonWebToken(algorithms=[alg])
    header = {'alg': alg}
    if isinstance(key, dict):
        # JWK set format
        if 'keys' in key:
            key = random.choice(key['keys'])
            header['kid'] = key['kid']
        elif 'kid' in key:
            header['kid'] = key['kid']

    return to_native(jwt.encode(header, payload, key))
Example #10
0
    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()
Example #11
0
    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)
Example #12
0
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)
Example #13
0
    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('/')
Example #15
0
    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)
Example #16
0
    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)
Example #17
0
async def get_subject_by_provider(request: Request, provider_name: str,
                                  token_str: str) -> str:
    if provider_name.startswith(
            f"cognito-idp.{request.app.state.region}.amazonaws.com/"):
        try:
            jwks = await get_jwks(
                "https://{provider_name}/.well-known/jwks.json")
            token = JsonWebToken().decode(token_str, key=jwks)
            token.validate_iss()
            token.validate_sub()
            token.validate_exp(request.scope[NOW_KEY].timestamp(), LEEWAY)
        except (httpx.HTTPError, JoseError):
            logger.error("failed to validate token", exc_info=True)
            raise NotAuthorizedException(
                "Invalid login token. Not a valid OpenId Connect identity token."
            )
    else:
        raise NotImplementedError()
Example #18
0
async def get_subject_by_provider(request: Request, provider_name: str,
                                  token_str: str) -> str:
    m = COGNITO_IDP_ENDPOINT_URL_RE.match(provider_name)
    if m is not None:
        url_base = (request.app.state.user_pool_emulator_url_base.rstrip("/") +
                    "/" + m.group("pool_id"))
        try:
            jwks = await get_jwks(f"{url_base}/.well-known/jwks.json")
            token = JsonWebToken().decode(token_str, key=jwks)
            token.validate_iss()
            token.validate_sub()
            token.validate_exp(request.scope[NOW_KEY].timestamp(), LEEWAY)
        except (httpx.HTTPError, JoseError):
            logger.error("failed to validate token", exc_info=True)
            raise NotAuthorizedException(
                "Invalid login token. Not a valid OpenId Connect identity token."
            )
        return token["sub"]
    else:
        raise NotImplementedError()
Example #19
0
 def decode(cls, token: str):
     claims = JsonWebToken(settings.SECURED_VIEW_JWT_ALGORITHM).decode(
         token, settings.SECURED_VIEW_JWK)
     claims.validate()
     return claims
Example #20
0
    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
        )
 def __init__(self, now: Callable[[], datetime], secret: str) -> None:
     self._now = now
     self._secret = secret
     self._jwt = JsonWebToken(['HS256'])
Example #22
0
from authlib.jose import jwt  # It is already a JsonWebToken object
from authlib.jose import JsonWebToken

# Encoding

# good - key and algorithm supplied
jwt.encode({"alg": "HS256"}, token, "key")
JsonWebToken().encode({"alg": "HS256"}, token, "key")

# bad - empty key
jwt.encode({"alg": "HS256"}, token, "")
JsonWebToken().encode({"alg": "HS256"}, token, "")

# Decoding

# good -  "it will raise BadSignatureError when signature doesn’t match"
jwt.decode(token, key)
JsonWebToken().decode(token, key)
Example #23
0
    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
Example #24
0
def make_jwt(payload):
    jwt = JsonWebToken(['RS256'])
    jws = jwt.encode(
        {'alg': 'RS256', 'kid': key.as_dict(add_kid=True).get('kid')}, payload, key=key)
    return jws