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.55k stars 452 forks source link

Make bearer token expiry optional #311

Closed ThiefMaster closed 3 years ago

ThiefMaster commented 3 years ago

Is your feature request related to a problem? Please describe.

Just like e.g. in the GitHub API I would like to be able to return tokens that do not expire. Depending on the app this is a perfectly valid case and as long as tokens can be revoked and TLS is used, the extra hassle of using refresh tokens is not needed - if an access token leaks, the refresh token would leak as well, since they are likely stored in the same place and sent to the same server anyway.

Describe the solution you'd like

I'd like to be able to return a None expiry or have some other way to specify that a bearer token should not have an expiry.

Describe alternatives you've considered

Setting a very long expiration time would work, but it still includes the expires_in in the token

Additional context

The RFC indicates that expires_in is just RECOMMENDED, not REQUIRED:

expires_in
         RECOMMENDED.  The lifetime in seconds of the access token.  For
         example, the value "3600" denotes that the access token will
         expire in one hour from the time the response was generated.
         If omitted, the authorization server SHOULD provide the
         expiration time via other means or document the default value.

The SHOULD part at the end would then of course be up to the application implementing the OAuth server, e.g. by documenting that it issues non-expiring tokens.


I saw in this gist that you already made some changes to the TokenMixin so so authlib 1.0 would be a good opportunity to add this functionality. If you agree, I could have a look into sending a PR for this.

lepture commented 3 years ago

@ThiefMaster PR is welcomed.

ThiefMaster commented 3 years ago

OK, this was much less work than I thought it would be - see the linked PR.

I tested the code from my PR using the example-oauth2-server app with these changes:

diff --git a/app.py b/app.py
index b9978ff..cdf67c2 100644
--- a/app.py
+++ b/app.py
@@ -4,6 +4,9 @@ from website.app import create_app
 app = create_app({
     'SECRET_KEY': 'secret',
     'OAUTH2_REFRESH_TOKEN_GENERATOR': True,
+    'OAUTH2_TOKEN_EXPIRES_IN': {
+        'authorization_code': 0,
+    },
     'SQLALCHEMY_TRACK_MODIFICATIONS': False,
     'SQLALCHEMY_DATABASE_URI': 'sqlite:///db.sqlite',
 })
diff --git a/website/routes.py b/website/routes.py
index 7f0f325..6884f50 100644
--- a/website/routes.py
+++ b/website/routes.py
@@ -98,7 +98,7 @@ def authorize():
         return redirect(url_for('website.routes.home', next=request.url))
     if request.method == 'GET':
         try:
-            grant = authorization.validate_consent_request(end_user=user)
+            grant = authorization.get_consent_grant(end_user=user)
         except OAuth2Error as error:
             return error.error
         return render_template('authorize.html', user=user, grant=grant)

Output when fetching the token:

[adrian@claptrap:~/dev/example-oauth2-server:master *%]> http -f --auth 'xxx:yyy' post http://127.0.0.1:5000/oauth/token 'grant_type=authorization_code' 'code=zzz'
HTTP/1.0 200 OK
Cache-Control: no-store
Content-Length: 106
Content-Type: application/json
Date: Sat, 23 Jan 2021 15:19:56 GMT
Pragma: no-cache
Server: Werkzeug/1.0.1 Python/3.9.0

{
    "access_token": "a random token used to be here",
    "scope": "profile",
    "token_type": "Bearer"
}