okta / okta-jwt-verifier-python

okta-jwt-verifier-python
https://github.com/okta/okta-jwt-verifier-python
Apache License 2.0
33 stars 17 forks source link

Is this library applicable for use with openid-connect providers other than okta? #38

Open evgenyfadeev opened 2 years ago

evgenyfadeev commented 2 years ago

Just a question - as is the title. I'm looking for a generic implementation of openid-connect. Can the token verifier be configured for use with other oidc providers?

Thanks!

serhiibuniak-okta commented 2 years ago

@evgenyfadeev This library designed to be used with Okta, thus some features (for example, get jwk for verifying signature) may not work with other oidc providers (the only working case - URI for jwk is being constructed in the same way). On the other hand, it is possible to use this library and override all methods you might need. Also, there is a separate module https://github.com/okta/okta-jwt-verifier-python/blob/master/okta_jwt_verifier/jwt_utils.py which have common methods such as parse_token and should work with any jwt.

evgenyfadeev commented 2 years ago

Thanks! Perhaps you could set the jwk_url as an additional parameter to IDTokenVerifier and AccessTokenVerifier? I could make a PR for that...

serhiibuniak-okta commented 2 years ago

That doesn't align with our design. On the other hand you can inherit and override anything.

evgenyfadeev commented 2 years ago

This worked for me - a subclass of BaseJWTVerifier that receives jwks_uri and overrides the _construct_jwks_uri method.

Later to validate the tokens I called the verify_access_token and verify_id_token the same way as in the

Btw, not sure why you're not relying on the discovery data for the jwks_uri instead of making assumptions about this url.

from okta_jwt_verifier import BaseJWTVerifier

class JWTVerifier(BaseJWTVerifier):
    """Wrapper around the `BaseJwtVerifier`
    main purpose is to allow passing the `jwks_uri`
    and not be making an assumption about the url structure.
    """

    def __init__(self,
                 issuer=None,
                 client_id=None,
                 jwks_uri=None,
                 audience=None,
                 max_retries=1,
                 request_timeout=30,
                 max_requests=10,
                 leeway=120,
                 cache_jwks=True,
                 proxy=None):

        self.jwks_uri = jwks_uri
        super().__init__(issuer=issuer,
                         client_id=client_id,
                         audience=audience,
                         max_retries=max_retries,
                         request_timeout=request_timeout,
                         max_requests=max_requests,
                         leeway=leeway,
                         cache_jwks=cache_jwks,
                         proxy=proxy)

    def _construct_jwks_uri(self):
        """Bypasses the okta assumptions"""
        return self.jwks_uri`
serhiibuniak-okta commented 2 years ago

@evgenyfadeev I'm glad that you've found a solution. Do you mean an additional network call by "discovery data for the jwks_uri"? In this case, we don't need unnecessary network calls: this jwks uri for Okta's orgs has been designed in that way and hasn't been changed for years. What I see from your approach - you can tweak it easily for your needs, I believe, it's fine. I can suggest one more approach: basically, in order to validate token you need to perform the following steps:

  1. Parse token
  2. Verify claims
  3. Verify signature (for this step you need jwk, so you can use anything you want/need to get it)

All of these steps are defined within okta_jwt_verifier/jwt_utils.py, so in theory you can write something like this (it's just a sample pseudocode):

from okta_jwt_verifier import JWTUtils

def verify_access_token(token, jwk):
    headers, claims, signing_input, signature = JWTUtils.parse_token(token)
    JWTUtils.verify_claims(claims, claims_to_verify=['iss', 'aud', 'exp'])
    JWTUtils.verify_signature(token, jwk)
kadhir-p44 commented 2 years ago

@all can we use this library in flask based web application synchronously since most methods are asynchronous in my case i need verify signature each and every call but my public key is available in remote server.

 do i need to do following steps 

          1. I need to download JWK from server then  I need to pass key explicitly
               JWTUtils.verify_signature(token, jwk)
serhiibuniak-okta commented 2 years ago

@kadhir-p44 There are few options: 1) https://flask.palletsprojects.com/en/2.0.x/async-await/ 2) use asyncio.run to call async methods 3) maybe something else is possible within Flask, but need to investigate

kadhir-p44 commented 2 years ago

@kadhir-p44 There are few options:

  1. https://flask.palletsprojects.com/en/2.0.x/async-await/
  2. use asyncio.run to call async methods
  3. maybe something else is possible within Flask, but need to investigate

@serhiibunaik

my concern is every request i need call asyncio.run () eventually it create another thread and i need to wait for result and it cause slow down my service isn't it? i expect something like java library

meanwhile i am getting following error on python 3.8 and ubuntu 20.04

okta_jwt_verifier.exceptions.JWTValidationException: 0, message='Attempt to decode JSON with unexpected mimetype: text/plain;charset=utf-8', url=URL('https://****/oauth2/v1/keys')

bretterer commented 2 years ago

Regarding your error you are receiving. Can you run:

import aiohttp
import asyncio

async def main():
    keys_url = 'https://{YOUR_ORG}/oauth2/default/v1/keys'
    async with aiohttp.ClientSession() as session:
        async with await session.get(keys_url) as resp:
            print(await resp.json())

asyncio.run(main())

And let us know what is returned? If the above returns an error still, could you try swapping the resp.json to be await resp.json(content_type=None)