lepture / authlib

The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS,JWE,JWK,JWA,JWT included.
https://authlib.org/
BSD 3-Clause "New" or "Revised" License
4.45k stars 445 forks source link

How to check for JWT expiration without checking the signature? #600

Open dolfinus opened 8 months ago

dolfinus commented 8 months ago

Hi.

I'm using authlib in Python client for a REST API implementing OAuth 2.0. Usage is simple as:

my_python_client = MyPythonClient(url="http://domain.com/api", token="some.jwt.token")
my_python_client.whoami()

Method whoami sends request to backend. It is using authlib.integrations.requests_client.OAuth2Session to wrap all auth interaction:

session = OAuth2Session()
session.token = AuthlibToken.from_dict(
    {
        "access_token": self.token,
        "token_type": "Bearer",
    },
)

session.request(self.url, ...)

Backend decodes this token and uses payload to get user data.

But if token is expired, I only get 401 error from backend indicating that token is invalid. This is not very good in terms if UX/DX.

How can I check that token is expired or malformed before it will be send to backend? To both show user a proper message on client side, and to raise exception of specific type (e.g. TokenExpiredError) which can be handled by developer.

Also without passing AuthlibToken.from_dict({"expires_at": ...}) authlib cannot detect that token is expired and another token should be issued instead (if using refresh_token, client_credentials and so on to automatically issue new access_token after expiration).

Currently the only way I could implement this is using python-jose:

token_decoded = jwt.decode(self.token, key="NONE", options={"verify_signature": False})
session.token = AuthlibToken.from_dict(
    {
        "access_token": self.token,
        "token_type": "Bearer",
        "expires_at": token_decoded["exp"],
    },
)

jwt.decode raises an exception jose.exceptions.ExpiredSignatureError: Signature has expired. Passing "verify_signature": False allows to decode token without checking its signature (which client cannot know in this case, secret key is private and used by backend only).

I've tried to use:

from authlib.jose.rfc7519.jwt import JsonWebToken
jwt = JsonWebToken(algorithms=None)

jwt.deserialize(token, key="")

or

from authlib.jose.rfc7515.jws import JsonWebSignature

signature = JsonWebSignature(algorithms=)
signature.deserialize(token, key="")

But they both raise authlib.jose.errors.BadSignatureError: bad_signature: because I didn't provide corresponding secret key to validate signature (which is intentional).

I also cannot pass algorithms=["NONE"] to any of those classes constructor to use key=None because real algorithm is determined using token content, and if it is not present in algorithms list an exception is raised authlib.jose.errors.UnsupportedAlgorithmError: unsupported_algorithm:.

So is there any way to check if token is expired without checking the signature?