Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrating from python-jose #25

Open
maciejstromich opened this issue Sep 9, 2024 · 4 comments
Open

Migrating from python-jose #25

maciejstromich opened this issue Sep 9, 2024 · 4 comments

Comments

@maciejstromich
Copy link

maciejstromich commented Sep 9, 2024

Currently doing migration from python-jose to joserfc and found myself in a bit of a pickle.

python-jose implementation allows validation of the token against the public_key (using verify method from RSAKey https://github.com/mpdavis/python-jose/blob/master/jose/backends/rsa_backend.py#L206).

As our codebase does that I was looking for a way to achieve the same with joserfc but the only reference to RSAKey verification I found in the docs was in https://jose.authlib.org/en/guide/jwk/#options
where I can pass additional parameters to the RSAKey.import_key but it does not explain exactly how does the verification works. Could you shed some additional light on how to approach this issue?

here's a pseudo code

def validate(public_key: jwk.RSAKey, token: bytes) -> bool:
    """Validate a token against a public key"""
    message, encoded_signature = token.rsplit(b".", 1)
    decoded_signature = base64url_decode(encoded_signature)
    return public_key.verify(message, decoded_signature)
@swails
Copy link

swails commented Sep 9, 2024

I've used jwt.decode() to do this:

def validate(public_key: jwk.RSAKey, token: str) -> bool:
    try:
        jwt.decode(token, public_key)
    except BadSignatureError:
        return False
    except JoseError:
        raise  # This will hit if, for instance, the token is an invalid token
    else:
        return True

This will just validate the signature. I combine this with a jwt.JWTClaimsRegistry in order to do further validation of the token. This will catch things like token expiry, and you can configure it to check for the existence, and value, of certain claims like the issuer or audience.

@lepture
Copy link
Member

lepture commented Sep 10, 2024

@swails is correct. It would be great if you can show me how are you using python-jose, I'll add a documentation about migration from python-jose, just like https://jose.authlib.org/en/migrations/pyjwt/

@swails
Copy link

swails commented Sep 10, 2024

The title of this issue is sufficient for me to share some of my migration experience from python-jose, so I'll just drop them here.

jose.jwt.get_unverified_header

The unverified header gives the signing key ID, so I grab the expected signing key by fetching them from the issuer and grabbing the one whose id matches what's in the header (but obviously you can't verify the token before you know which key to use).

# Equivalent to `jose.jwt.get_unverified_header`
from typing import Any
from joserfc import jws

def get_unverified_header(token: str) -> dict[str, Any]:
    token_content = jws.extract_compact(token.encode())
    return token_content.protected

jose.jwt.get_unverified_claims

I created an additional identity provider with the capability to create and sign tokens (storing a signing key in AWS KMS in a hardware security module). The signing key was either an Azure signing key minted by Microsoft or it was the signing key I created in AWS. In order to identify which key I should use to verify, I needed to check the issuer (it would either be Microsoft or my own). Then I would know which issuer to fetch public keys from so I could verify the signature.

# Equivalent to `jose.jwt.get_unverified_claims`
from typing import Any
from joserfc import jws

def get_unverified_claims(token: str) -> dict[str, Any]:
    token_content = jws.extract_compact(token.encode())
    return json.loads(token_content.payload)

@maciejstromich
Copy link
Author

thanks @swails. That helped a lot to straighten my spaghetti approach :-)

I will add also

jose.jwk.construct

from joserfc import jwk

def return_jwks_keys():
    parameters = {"use": "sig", "alg": "RS256", "key_ops": ["verify"]}
    return {item["kid"]: jwk.RSAKey.import_key(item, parameters=parameters) for item in jwks_json}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants