Giter Club home page Giter Club logo

authlib's People

Contributors

azmeuk avatar chromakode avatar dhallam avatar dp-rufus avatar dustydecapod avatar greyli avatar isccarrasco avatar jaap3 avatar jacopo-exact avatar ktosiek avatar lepture avatar marcrleonard avatar maxzhenzhera avatar mjos avatar nfvs avatar ngnpope avatar pablogamboa avatar pmarti avatar prilkop avatar princekhunt avatar raphaelahrens avatar rogiervandergeer avatar rostikl avatar spivachuk avatar timgates42 avatar tomchristie avatar turnrdev avatar venthur avatar xrmx avatar zeldovich avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

authlib's Issues

`OAuth2Session.add_token()` does not exist

  File "/Users/nsantos/.virtualenvs/oauth/lib/python3.6/site-packages/authlib/client/oauth2.py", line 293, in request
    url, headers, data, self.token_placement
TypeError: 'NoneType' object is not callable

This is the snippet that triggers that (in request()):

            url, headers, data = self.add_token(
                self.token['access_token'],
                url, headers, data, self.token_placement
            )

OAuth2Session.add_token does not exist (it's None).

AGPL?!

I just realized that this library is AGPL-licensed - quite unexpected considering that its predecessors like flask-oauthlib and oauthlib are BSD-licensed, and e.g. flask-oauthlib strongly recommends people to authlib instead.

While I completely understand that you want to make money with this library when people use it in commercial/closed-source software, the fact that it's AGPL and thus very viral seems problematic:

For example, many open source projects nowadays use a more liberal license like MIT or BSD.
And while IANAL, I'm pretty sure any such projects are currently excluded from using your library, since you cannot use GPL software in MIT/BSD-licensed application...

...which this is truly unfortunate, since AFAIK there is no other decent implementation of OAuth providers for Python out there - and many webapps do include OAuth provider functionality nowadays! And we all know what happens when people start implementing their own security code... usually it won't be as secure as it should be.

Deprecate built-in Cache system

Per pallets/werkzeug#1249

Werkzeug is going to deprecate werkzeug.contrib.cache, there is not an easy way to maintain a built-in cache system for Flask. In this case, it is suggested that you pass a cache instance as parameter.

Cache Interface

You need to create your own cache object. A cache interface should contain these methods:

  • .get(key)
  • .delete(key)
  • .set(key, value, timeout=None)

Flask OAuth Client

If you need to support OAuth 1, cache is required. You can pass the cache object when initializing or init_app:

from authlib.flask.client import OAuth

oauth = OAuth(app, cache=cache)

# or initialize lazily
oauth = OAuth()
oauth.init_app(app, cache=cache)

Flask OAuth 1 Server

Cache is used a lot in OAuth 1 provider. Instead of passing cache as a parameter,
you can use the helper functions to register hooks:

from authlib.flask.oauth1 import AuthorizationServer, ResourceProtector
from authlib.flask.cache import (
    register_nonce_hooks,
    register_temporary_credential_hooks,
    create_exists_nonce_func
)

# 1. AuthorizationServer
server = AuthorizationServer(app, Client)
register_nonce_hooks(server, cache)
register_temporary_credential_hooks(server, cache)

# 2. ResourceProtector
require_oauth = ResourceProtector(
    app, Client,
    query_token=query_token,
    exists_nonce=create_exists_nonce_func(cache)
)

Flask OAuth 2 Server

Pass cache instead of Flask app into register_cache_authorization_code:

from authlib.flask.oauth2 import register_cache_authorization_code

register_cache_authorization_code(cache, server, create_access_token)

The built-in cache system will be removed in version 0.7.

IDToken.validate_exp raises IDTokenError when exp is in the future

Doesn't the logic below need to flipped so that it only raises an IDTokenError when the exp is less than now (i.e. when the token expiration time is before now)?

    def validate_exp(self, now):
        if 'exp' not in self.token:
            raise IDTokenError('exp is required')
        if now and self.exp > now:  # <--- Shouldn't this be <= ?
            raise IDTokenError('exp is expired')

From OIDC 1.0:

exp
REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing.

Version 0.4

With Authlib 0.3, we can create an OAuth 2 server in Flask framework. I'm not going to implement Django integration by version 0.6. In version 0.4, I will focus on OAuth 1 server.

Tasks in version 0.4:

  • RFC5849, The OAuth 1.0 Protocol.
  • Flask servers
  • Test cases
  • Documentation

Version 0.5

With Authlib 0.4, we can create an OAuth 1 server in Flask framework. Before starting version 0.5, there are remaining tasks for v0.4:

In Version 0.5. I will focus on stability, API design, bug fixes, and documentation improvement. New features are also important, but I need to figure out what is more important.

Here are somethings in my mind:

  • OAuth 1 enhancement with realms
  • Create an OAuth2Request wrapper
  • Client apps design and more apps

And it's time to remove the deprecated things in version 0.3.

Add `project_urls` in setup.py

Warehouse can show GitHub status.

project_urls={
    "Bug Tracker": "https://github.com/lepture/authlib/issues",
    "Documentation": "https://docs.authib.org/",
    "Source Code": "https://github.com/lepture/authlib",
}

OIDC compliance for flask server: POST to /authorize does not use form data

The README lists OpenID Connect as an implemented feature, so I assume this implementation is meant to be fully OIDC compliant. However, the default flask implementation doesn't get the request parameters from the form data, as described by OIDC spec:

If using the HTTP POST method, the request parameters are serialized using Form Serialization, per Section 13.2.

Here is my understanding what happens currently for the flask server handling a POST to /authorize:

It seems like it would be straightforward to check flask.request.method and grab the params from flask.request.form if the method is POST. I'd be happy to submit a PR for this if that would be welcomed.

Version 0.7

In this version, Authlib will focus on JWT enhancement. There are also deprecated APIs which should be removed in this version.

RFC

  1. RFC7515, implement full featured JWS
  2. RFC7523, JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants

Integrations

  • RFC7521 AssertionSession
  • RFC7523 for AssertionSession
  • Flask server for RFC7521

Others

  • crypto backend preparation
  • loginpass v0.1

Allow bypassing of https check for dev purposes

Servers like Hydra support http mode for development purposes. It'd be great if there were some way to bypass the authlib.specs.rfc6749.InsecureTransportError check for local development.

Improvement in extract authorization token

In urls.py

def extract_basic_authorization(token):
    """Extract token from Basic Authorization."""
    query = to_unicode(base64.b64decode(token))
    if ':' in query:
        return query.split(':', 1)
    return query, None

If user pass the invalid token will cause internal server error, could you use try catch to catch the exception?

Deprecate client_model

There is no need to pass the Client model class into servers. Here is the upgrade guide.

Flask OAuth 1 Server

Flask OAuth 1 server has been redesigned a lot. It's better to read the documentation again.

Flask OAuth 2 Server

Pass a query_client function to AuthorizationServer instead of client_model:

from authlib.flask.oauth2 import AuthorizationServer
from your_project.models import Client

def query_client(client_id):
    return Client.query.filter_by(client_id=client_id).first()

server = AuthorizationServer(app, query_client)
# or lazily
server = AuthorizationServer()
server.init_app(app, query_client)

There is a helper function to create query_client:

from authlib.flask.oauth2.sqla import create_query_client_func

query_client = create_query_client_func(db.session, Client)

Align documentation with samples

Hey,

First of, thank you for this package! It's really useful.

I just wanted to report that the documentation is somtimes a bit lacking in places. I have been trying to use the Flask server support along with the GitHub integration and I keep getting a problem with the code being expired from GH.

I thought I'd look at the playground example but realised that it was quite different from the docs. There are aspects (like caching) that aren't setup as per the docs and it's unclear what needs to actually be done.

Is the current doc up to date and authoritative or should I derive from the playground?

Thanks,

google.parse_openid() sometimes can't parse JWK

I'm using oauth2 with Google as the provider in a flask app and everything works fine (authlib 0.7). However, after a few days, the oauth2 callback from google can not be read any more and the following error is produced:

[2018-05-02 07:05:48,173] ERROR in app: Exception on /oauth2callback [GET]
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.5/dist-packages/flask/_compat.py", line 33, in reraise
raise value
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1612, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1598, in dispatch_request
return self.view_functionsrule.endpoint
File "/app/ms.py", line 209, in authorized
user = google.parse_openid(token)
File "/usr/local/lib/python3.5/dist-packages/authlib/client/apps/_google.py", line 33, in parse_openid
claims_params=claims_params,
File "/usr/local/lib/python3.5/dist-packages/authlib/specs/rfc7519/jwt.py", line 95, in decode
data = self.deserialize_compact(s, _wrap_key(key))
File "/usr/local/lib/python3.5/dist-packages/authlib/specs/rfc7515/jws.py", line 95, in deserialize_compact
algorithm, key = self._prepare_algorithm_key(header, payload, key)
File "/usr/local/lib/python3.5/dist-packages/authlib/specs/rfc7515/jws.py", line 276, in _prepare_algorithm_key
key = key(header, payload)
File "/usr/local/lib/python3.5/dist-packages/authlib/specs/rfc7519/jwt.py", line 123, in key_func
return _load_jwk(key, header)
File "/usr/local/lib/python3.5/dist-packages/authlib/specs/rfc7519/jwt.py", line 111, in _load_jwk
return jwk.loads(key, header.get('kid'))
File "/usr/local/lib/python3.5/dist-packages/authlib/specs/rfc7517/jwk.py", line 54, in loads
raise ValueError('Invalid JWK format')
ValueError: Invalid JWK format

The callback-URL google redirected to, was the following (tokens changed):

https://myserver/oauth2callback?state=sjyizeyuwnyGtk8LeNi1M7k8LrBycoOTIeizOfC&code=4/AABVzpc4cbxwLowSprM90y6GOgmou2VpLHdBPcmMGfazaoei0kaSZUgR9Haz-jzXooLRu0_gpBPOrORzYi8qQ5o&authuser=0&hd=mydomain.com&session_state=aa96e41144175e48151746b9df23609fcc985905..97cb&prompt=none#

It seems like authlib is struggling to parse the answer from google for some reason.

Version 0.3

We have finished the client part in Version 0.2 #2. Starting from version 0.3, we will focus on implementation of the server part.

Tasks of version 0.3:

  • Specification of Authorization Code Flow of OAuth 2
  • Specification of Implicit Flow of OAuth 2
  • Specification of Password Flow of OAuth 2
  • Specification of Client Credential Flow of OAuth 2
  • Bearer Token rfc6750
  • Authorization Server
  • Resource Server

Note: take OpenID Connect into account when designing these specifications

Also, we need a real provider in Flask:

  • Implement an OAuth 2 provider in Flask.

  • Test cases for OAuth 2 provider
  • Documentation

Deadline: The end of this year.

Features Checklist

Authlib features and their statuses.

Client

  • OAuth1Session, compatible with requests-oauthlib
  • OAuth2Session, compatible with requests-oauthlib
  • OAuthClient, a mixed client for OAuth 1 and OAuth 2
  • Flask integration
  • Django integration
  • OpenID Connect id_token parsing

Server

  • Flask OAuth 1 provider
  • Flask OAuth 2 provider
  • Django OAuth 1 provider
  • Django OAuth 2 provider
  • Flask OpenID integration
  • Django OpenID integration

Specifications

  • RFC4226
  • RFC5849
  • RFC6238
  • RFC6749
  • RFC6750
  • RFC7009
  • RFC7515, JSON Web Signature
  • RFC7516, JSON Web Encryption
  • RFC7517, JSON Web Key
  • RFC7518, JSON Web Algorithms
  • RFC7519, JSON Web Token
  • RFC7521
  • RFC7522
  • RFC7523
  • RFC7591
  • RFC7592
  • RFC7636
  • RFC7662
  • RFC7797

New Plan for Version 0.6

Plan changed. Original plan was adding Django integration #24.

Django integration will be delayed. Version 0.6 will focus on JOSE stuffs. The JOSE RFCs are required for many other OAuth 2 related RFCs, including Dynamic Client Registration Protocol, Assertion Framework, Open ID Connect and so on.

In version 0.6, I would like to add these features:

  • RFC7515: JSON Web Signature (JWS)
  • RFC7517: JSON Web Key (JWK)
  • RFC7518: JSON Web Algorithms (JWA)
    • JWS
    • JWK
  • RFC7519: JSON Web Token (JWT)

They will not be full featured, but they would work for the most cases. With these specs added, I'll add a simple OpenID Connect server:

  • Code Flow
  • Implicit Flow
  • Hybrid Flow

Flask RemoteApp.authorize_access_token() includes state parameter in request

When following the typical authorization flow, the access token is requested from the authorization redirect URI. The RemoteApp implementation for Flask copies all request arguments of this callback and attaches them to the request for the authorization token.

@app.route('/authorize')
def authorize():
    token = oauth.google.authorize_access_token()
    # ...

params = request.args.to_dict(flat=True)

The request arguments to the callback include "state", which does not need to be submitted with the request for the access token. Due to this, the Google API will respond with an error: "Parameter not allowed for this message type: state".

Cannot log in with Facebook: "Missing client_id parameter."

From authorize_access_token call I receive:
'{"error":{"message":"Missing client_id parameter.","type":"OAuthException","code":101,"fbtrace_id":"AkqIKeKkCiT"}}'

During debug I found in oauth.py fetch_access_token(...), when body object is formatted it has one of the query parameters "client=....", which in fact should be "client_id=..." for Facebook.
Unfortunately, there is no compliance_hook here for it.

I would suggest to make a complience_hook for it. In fact I want to contribute to authlib by myself.

pop withhold_token before making request

In OAuthClient.request, the withhold_token arg appears to be internal, but it is passed on as a param in the request.

if kwargs.get('withhold_token'):
return session.request(method, url, **kwargs)

Should this be popped?

if kwargs.pop('withhold_token', False):
    return session.request(method, url, **kwargs)

Make ResourceProtector extensible

Only BearerToken is supported currently, it is required to make the protector extensible so that we can add more token type later.

OAuth2ClientMixin requires a client_secret

  1. From https://play.authlib.org/
  2. Select Apps
  3. Select New Oauth2 Client
  4. Fill in the fields without checking the box Confidential Client
  5. Click Create

You get:
Internal Server Error
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

Seems like this is due to OAuth2ClientMixin requiring a client secret

No way to generate tokens containing user data

In some use cases it's nice to be able to generate tokens that are not stored in a database but just contain signed data.

However, the AuthorizationServer has this code in init_app:

self.generate_token = self.create_bearer_token_generator(app)

The function it calls only takes a string from the config which points to a function not accepting any arguments, so even if I specify a custom function to generate a token I cannot include any user data in it.

Assigning a custom function to self.generate_token is also problematic because it's assigned in init_app, which means I need to do it quite late - when I'm calling init_app of Flask extensions, I'm usually done setting everything up and do not expect having to assign a function only after that. Having to assign functions after calling init_app feels rather dirty. This is stuff people usually do at import time, not at config loading / app initialization time. Also, when using an app factory I might end up calling it more than once, so changing global state from there is even worse.

Cyclic dependencies when using AuthorizationServer(Client, app)

At least in my case, when creating an AuthorizationServer, it requires the Client model. This has the requirement that the models be imported. However, since the models themselves also import __init__.py through from .. import db, login_manager, it creates a cyclic include.

Due to this constraint, the next best option is to use flask-oauthlib library. It might make sense to address this issue, either in the documentation or in the code architecturally.

`TypeError` when refreshing a token with no `refresh_token_params`

  File "/Users/nsantos/.virtualenvs/oauth/lib/python3.6/site-packages/authlib/client/oauth2.py", line 288, in request
    self.refresh_token_url, auth=auth, **kwargs
  File "/Users/nsantos/.virtualenvs/oauth/lib/python3.6/site-packages/authlib/client/oauth2.py", line 209, in refresh_token
    kwargs.update(self.refresh_token_params)
TypeError: 'NoneType' object is not iterable

In OAuth2Session's constructor refresh_token_params can be set to None, which will trigger the above bug.

Introspection endpoint doesn't follow RFCs for invalid token

Hello,

I'm not doing a PR as I don't know how do you want to correct this issue (serveral possibilities). Here:

raise InvalidRequestError()

when the token doesn't exist it should return a 200 with active=false instead of an invalid request as the RFC mention it:

If the introspection call is properly authorized but the token is not active, does not exist on this server, r the protected resource is not allowed to introspect this particular token, then the authorization server MUST return an introspection response with the "active" field set to "false"

On my side I just did return a special value in query_token and handled it in introspect_token.
Thanks.

Rémi

Should password grant flow require client id and secret?

I am working on the password grant flow and I found that the library require me to put client id and secret into header even I am using password grant flow, otherwise it would return "invalid_client" as response. Is this a mistake?

Version 0.2

I'm planning to finish all features at Version 0.5. Version 0.2 will still focus on the client part, and we will start the implementation of server/provider from Version 0.3.

  • Finish Django Client
  • Documentation on Django Client
  • Simple OpenID Connect support (id_token parsing)
  • Documentation on id_token parsing
  • Making Client Apps stable

When the client part is finished, we will deploy a playground website for OAuth authentication.
It will be implemented with the Authlib Flask client.

Authenticate client within the ClientMixin

From RFC6749: "The authorization server MAY accept any form of client authentication meeting its security requirements.".

However given a client query and a request object, the authlib.specs.rfc6749.authenticate_client.authenticate_client function tries to apply multiple authentication methods to the client hopping that one of them will succeed. But the actual authentication method to use is not known and may be application specific.

I think that authentication should be delegated to the client itself (authlib.specs.rfc6749.models.ClientMixin) and developers will implement the correct authentication method depending of the type of client.

Common helpers can be provided through.

Version 0.8

In this version, it will focus on Django OAuth 1 and OAuth 2 servers/providers. Authlib itself won't be a Django app, it can only provide the same level integration as Flask. There would be another repo to make a Django app in the long run.

Tasks that should be done in this version:

  • Django OAuth 1 server
  • Django OAuth 2 server with Bearer Token / 4 grant types / refresh token / revoke token
  • Test cases
  • Documentation

error when calling generate_authorize_redirect

If I forgot to define config variables like GITHUB_CLIENT_ID, GITHUB_CLIENT_KEY, calling client.generate_authorize_redirect will result in this (I've included all the local variables in relevant function callings):

-> url, state = self.session.authorization_url(
(Pdb++) locals()
{'pdb': <module 'pdb' from '/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/_pdbpp_path_hack/pdb.py'>, 'kwargs': {}, 'save_request_token': None, 'callback_uri': 'http://citadel.test.ricebook.net/user/authorized', 'self': <authlib.flask.client.oauth.RemoteApp object at 0x110656898>}
(Pdb++) c
[24] > /Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/authlib/specs/rfc6749/parameters.py(67)prepare_grant_uri()
-> return add_params_to_uri(uri, params)
(Pdb++) locals()
{'pdb': <module 'pdb' from '/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/_pdbpp_path_hack/pdb.py'>, 'params': [('response_type', 'code'), ('client_id', None), ('redirect_uri', 'http://citadel.test.ricebook.net/user/authorized'), ('state', 'xxxxxxxx')], 'kwargs': {}, 'state': 'xxxxxxxx', 'scope': None, 'redirect_uri': 'http://citadel.test.ricebook.net/user/authorized', 'response_type': 'code', 'client_id': None, 'uri': None}
(Pdb++) c
[25] > /Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/authlib/common/urls.py(118)add_params_to_uri()
-> return urlparse.urlunparse((sch, net, path, par, query, fra))
(Pdb++) locals()
{'pdb': <module 'pdb' from '/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/_pdbpp_path_hack/pdb.py'>, 'fra': b'', 'query': 'response_type=code&client_id=None&redirect_uri=http%3A%2F%2Fcitadel.test.ricebook.net%2Fuser%2Fauthorized&state=xxxxxxxx', 'par': b'', 'path': b'', 'net': b'', 'sch': b'', 'fragment': False, 'params': [('response_type', 'code'), ('client_id', None), ('redirect_uri', 'http://citadel.test.ricebook.net/user/authorized'), ('state', 'xxxxxxxx')], 'uri': None}
(Pdb++) c
[2018-01-29 01:05:58 +0800] [72803] [INFO] [_internal.py @ 87]: 127.0.0.1 - - [29/Jan/2018 01:05:58] "GET /user/login HTTP/1.1" 500 -
[2018-01-29 01:05:58 +0800] [72803] [ERROR] [_internal.py @ 87]: Error on request:
Traceback (most recent call last):
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/werkzeug/serving.py", line 267, in run_wsgi
    execute(self.server.app)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/werkzeug/serving.py", line 255, in execute
    application_iter = app(environ, start_response)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask_sockets.py", line 48, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/timfeirg/eru/citadel/citadel/views/user.py", line 30, in login
    url_for('user.authorized', _external=True)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/authlib/client/client.py", line 100, in generate_authorize_redirect
    self.authorize_url, **kwargs)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/authlib/client/oauth2.py", line 111, in authorization_url
    state=state, **kwargs
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/authlib/specs/rfc6749/parameters.py", line 67, in prepare_grant_uri
    return add_params_to_uri(uri, params)
  File "/Users/timfeirg/.virtualenvs/citadel/lib/python3.6/site-packages/authlib/common/urls.py", line 118, in add_params_to_uri
    return urlparse.urlunparse((sch, net, path, par, query, fra))
  File "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/urllib/parse.py", line 454, in urlunparse
    _coerce_args(*components))
  File "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/urllib/parse.py", line 120, in _coerce_args
    raise TypeError("Cannot mix str and non-str arguments")
TypeError: Cannot mix str and non-str arguments

So if the user didn't realize there's a typo in the config, he would have to do some intensive debugging to realize what's actually wrong, so I think it's best to do some config validation, or some encoding conversion during authlib.common.urls.add_params_to_uri, or anything else to correctly expose the problem.

TypeError: update_token() got multiple values for argument 'name' in flask app

I wrote a update_token method according to the doc in my flask app, but it shows TypeError: update_token() got multiple values for argument 'name'. Here is the code:

def update_token(name, token):
    session['token'] = token

After debugging the source code I found that self.token_updater already contains the name in keyword, so I changed update_token(name, token) to update_token(token, name) and it works fine. Is the doc not updated or do I miss something? I am using authlib 0.7.

Fix error_uri implementation

The current implementation of error_uri is wrong. According to the definition:

  error_uri
         OPTIONAL.  A URI identifying a human-readable web page with
         information about the error, used to provide the client
         developer with additional information about the error.
         Values for the "error_uri" parameter MUST conform to the
         URI-reference syntax and thus MUST NOT include characters
         outside the set %x21 / %x23-5B / %x5D-7E.

OAuth2 ClientCredential grant custom expiration not being read from Flask configuration.

Summary

When registering a client credential grant for the authlib.flask.oauth2.AuthorizationServer in a Flask application and attempting to set a custom expiration time by setting OAUTH2_EXPIRES_CLIENT_CREDENTIAL as specified in the docs:

https://github.com/lepture/authlib/blob/23ea76a4d9099581cd1cb43e0a8a9a49a9328361/docs/flask/oauth2.rst#define-server

the specified custom expiration time is not being read.

Investigation

At first I believe that there was simply an error in the docs, in that the configuration value key should be pluralized, e.g. OAUTH2_EXPIRES_CLIENT_CREDENTIALS. However, upon a deeper look, it seems as though the default credential grant expiration time set in the authlib code base was not being used at all; the value being returned was 3600 instead of 864000 as specified in the mapping:

GRANT_TYPES_EXPIRES = {
'authorization_code': 864000,
'implicit': 3600,
'password': 864000,
'client_credential': 864000
}

After a bit more digging, it seems that the create_expires_generator is returning the default BearerToken.DEFAULT_EXPIRES_IN value because the calculated conf_key = 'OAUTH2_EXPIRES_{}'.format(grant_type.upper()) only produces client_credentials instead of the expected client_credential:

def create_expires_generator(self, app):
"""Create a generator function for generating ``expires_in`` value.
Developers can re-implement this method with a subclass if other means
required. The default expires_in value is defined by ``grant_type``,
different ``grant_type`` has different value. It can be configured
with: ``OAUTH2_EXPIRES_{{grant_type|upper}}``.
"""
def expires_in(client, grant_type):
conf_key = 'OAUTH2_EXPIRES_{}'.format(grant_type.upper())
return app.config.get(conf_key, BearerToken.DEFAULT_EXPIRES_IN)
return expires_in

Notes

A very subtle bug that took me a while to track down; I attempted to create a test case, but it's not very obvious as to where the test case should exist since tests/flask/test_oauth2/test_client_credentials_grant.py is composed of functional tests as opposed to integration/unit tests.

If this is at all not clear, please let me know and I'll attempt to provide additional information.

And thank you for all your hard work! Authlib is fantastic, and has been a pleasure to use even in it's not-completely-done state.

[Flask] using automatic token refresh failed.

I am trying to automatically refresh tokens stored in a postgres db as json.
With this example Flask app:

from flask import Flask, jsonify, url_for, redirect
from authlib.flask.client import OAuth

app = Flask(__name__)
oauth = OAuth(app)

def fetch_token():
    log.info('fetch_token')
    return current_user.token

def update_token(token):
    log.info('update_token')
    current_user.token = token
    return token

oauth.register(
    'google',
    api_base_url='https://www.googleapis.com/',
    access_token_url='https://www.googleapis.com/oauth2/v4/token',
    authorize_url='https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&include_granted_scopes=true&hd=*',
    refresh_token_url='https://www.googleapis.com/oauth2/v4/token',
    client_kwargs = {'scope': 'email profile'},
    fetch_token=fetch_token,
    update_token=update_token
)

@app.route('/')
def index():
    resp = oauth.google.get('/oauth2/v2/userinfo')
    data = resp.json()
    return jsonify(data)

@app.route('/login')
@login_required
def authorize():
    redirect_uri = url_for('.google', _external=True)
    return oauth.google.authorize_redirect(redirect_uri)


@app.route('/login/google/authorize')
def google():
    new_token = oauth.google.authorize_access_token()
    current_user.token = new_token
    current_user.save()
    return redirect(url_for('.index'))

First i ran into this error

Traceback (most recent call last):
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/flask_login/utils.py", line 261, in decorated_view
    return func(*args, **kwargs)
  File "/home/nebularazer/test_app/gsuite/controller/contacts.py", line 78, in index
    log.info('scopes %s', current_user.granted_scopes)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/werkzeug/local.py", line 347, in __getattr__
    return getattr(self._get_current_object(), name)
  File "/home/nebularazer/test_app/gsuite/models/user.py", line 78, in granted_scopes
    req = oauth.google.post('/oauth2/v2/tokeninfo', params=params)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/client/client.py", line 188, in post
    return self.request('POST', url, **kwargs)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/flask/client/oauth.py", line 181, in request
    method, url, token=token, **kwargs)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/client/client.py", line 170, in request
    return session.request(method, url, **kwargs)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/client/oauth2.py", line 306, in request
    self.refresh_token_url, auth=auth, **kwargs
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/client/oauth2.py", line 234, in refresh_token
    refresh_token=refresh_token, **kwargs
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/specs/rfc6749/parameters.py", line 107, in prepare_token_request
    return add_params_to_qs(body, params)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/common/urls.py", line 106, in add_params_to_qs
    return url_encode(qs)
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/common/urls.py", line 44, in url_encode
    encoded.append((to_bytes(k), to_bytes(v)))
  File "/home/nebularazer/test_app/.venv/lib/python3.6/site-packages/authlib/common/encoding.py", line 24, in to_bytes
    return byte_type(x)
TypeError: 'str' object cannot be interpreted as an integer

I was able to get around this by adding the following to this

def to_bytes(x, charset='utf-8', errors='strict'):
    if x is None:
        return None
    if isinstance(x, dict):  # <-- not sure if it is save to do so (the dict was the token)
        return None
    if isinstance(x, byte_type):
        return x
    if isinstance(x, unicode_type):
        return x.encode(charset, errors)
    if isinstance(x, (int, float)):
        return str(x).encode(charset, errors)
    return byte_type(x)

Then the refresh worked but update_token was never called.
I changed the following to make it work here

if self.refresh_token_url:
    self.client_kwargs['token_updater'] = update_token

I am not sure if these changes break something else, but i hope this can be fixed anyways :)
Or maybe i am using something wrong? If so, i would like to know what i did wrong.

edit: code snippet fix

Can't provide `redirect_uri` in `OAuth2Session.authorization_url()` call

The authorization_url method takes a kwargs as an argument in which we can provide redirect_uri. The flow is correctly wired. There's just the conflict of redirect_uri being passed separately in the prepare_grant_uri method which results in:

TypeError: prepare_grant_uri() got multiple values for keyword argument 'redirect_uri'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.