The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS,JWE,JWK,JWA,JWT included.

Authlib

Build Status Coverage Status PyPI Version Maintainability Follow Twitter

The ultimate Python library in building OAuth and OpenID Connect servers. JWS, JWK, JWA, JWT are included.

Authlib is compatible with Python2.7+ and Python3.6+.

Authlib v1.0 will only support Python 3.6+.

Sponsors

If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at auth0.com/developers.
A blogging and podcast hosting platform with minimal design but powerful features. Host your blog and Podcast with Typlog.com.

Fund Authlib to access additional features

Features

Generic, spec-compliant implementation to build clients and providers:

Connect third party OAuth providers with Authlib built-in client integrations:

Build your own OAuth 1.0, OAuth 2.0, and OpenID Connect providers:

Useful Links

  1. Homepage: https://authlib.org/.
  2. Documentation: https://docs.authlib.org/.
  3. Purchase Commercial License: https://authlib.org/plans.
  4. Blog: https://blog.authlib.org/.
  5. Twitter: https://twitter.com/authlib.
  6. StackOverflow: https://stackoverflow.com/questions/tagged/authlib.
  7. Other Repositories: https://github.com/authlib.
  8. Subscribe Tidelift: https://tidelift.com/subscription/pkg/pypi-authlib.

Security Reporting

If you found security bugs, please do not send a public issue or patch. You can send me email at [email protected]. Attachment with patch is welcome. My PGP Key fingerprint is:

72F8 E895 A70C EBDF 4F2A DFE0 7E55 E3E0 118B 2B4C

Or, you can use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.

License

Authlib offers two licenses:

  1. BSD (LICENSE)
  2. COMMERCIAL-LICENSE

Companies can purchase a commercial license at Authlib Plans.

If your company is creating a closed source OAuth provider, it is strongly suggested that your company purchasing a commercial license.

Support

If you need any help, you can always ask questions on StackOverflow with a tag of "Authlib". DO NOT ASK HELP IN GITHUB ISSUES.

We also provide commercial consulting and supports. You can find more information at https://authlib.org/support.

Owner
Hsiaoming Yang
This guy is too lazy to introduce himself.
Hsiaoming Yang
Comments
  • AGPL?!

    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.

  • Allowing multiple authentication flows in parallel (e.g., in different tabs)

    Allowing multiple authentication flows in parallel (e.g., in different tabs)

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

    We currently use authlib.integrations.flask_client to authenticate users against Auth0, where they can log in via different connection types (e.g., GitHub, Google, etc.). But users sometimes initiate multiple authentication flows in parallel, as by command- or control-clicking multiple links, each of which opens in a new tab. Even if the user logs in successfully to each of those tabs, each tab (except for the most recently opened one) ultimately errs with:

    authlib.integrations.base_client.errors.MismatchingStateError: 
    mismatching_state: CSRF Warning! State not equal in request and response
    

    That seems to be the result of Authlib using a single session key (e.g., _name_authlib_state_) to implement CSRF protection, per https://github.com/lepture/authlib/blob/master/authlib/integrations/flask_client/integration.py#L15-L21.

    Because each tab has a new value for state, previously opened tabs' state values are clobbered in Flask's session.

    Describe the solution you'd like

    Might it make sense to use unique session keys instead so that tabs don't share (and clobber each other's) state? Similarly for _name_authlib_redirect_uri_ and _name_authlib_nonce_? E.g., instead of, in pseudocode,

    session["_name_authlib_state_"] = "ABC"
    session["_name_authlib_redirect_uri_"] = "https://example.com/callback"
    session["_name_authlib_nonce_"] = "XYZ"
    

    perhaps an approach along these lines, whereby the key includes the unique state value?

    session["_name_authlib_ABC_"] = {
        "redirect_uri": "https://example.com/callback",
        "nonce": "XYZ"
    }
    

    Describe alternatives you've considered

    Instead of authlib.integrations.flask_client, should we perhaps use requests_client.OAuth2Session instead to achieve this behavior? Would we need to validate state, redirect_uri, and nonce ourselves, though, and also validate the ID token returned from Auth0?

    Is a simpler approach already possible perhaps?

    Additional context

    If a user opens multiple tabs because they've command- or control-clicked multiple links (each of which requires authentication), it's indeed intentional that we want them to authenticate in each of those tabs (especially since their choice of connection type on Auth0's end might differ per tab). We wouldn't want one tab, upon encountering a MismatchingStateError, simply to check whether the user has logged in via another tab and allow the user to proceed.

    Thank you!

  • CSRF Warning! State not equal in request and response

    CSRF Warning! State not equal in request and response

    Describe the bug

    It happens when using authlib to configure Keycloak for Apache Superset. Everything works perfectly up until redirecting back from Keycloak to Superset.

    Error Stacks

    Traceback (most recent call last):
      File "/home/abc/.local/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
        response = self.full_dispatch_request()
      File "/home/abc/.local/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
        rv = self.handle_user_exception(e)
      File "/home/abc/.local/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
        reraise(exc_type, exc_value, tb)
      File "/home/abc/.local/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
        raise value
      File "/home/abc/.local/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
        rv = self.dispatch_request()
      File "/home/abc/.local/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
        return self.view_functions[rule.endpoint](**req.view_args)
      File "/home/abc/.local/lib/python3.8/site-packages/flask_appbuilder/security/views.py", line 681, in oauth_authorized
        resp = self.appbuilder.sm.oauth_remotes[provider].authorize_access_token()
      File "/usr/local/lib/python3.8/site-packages/authlib/integrations/flask_client/remote_app.py", line 74, in authorize_access_token
        params = self.retrieve_access_token_params(flask_req, request_token)
      File "/usr/local/lib/python3.8/site-packages/authlib/integrations/base_client/base_app.py", line 145, in retrieve_access_token_params
        params = self._retrieve_oauth2_access_token_params(request, params)
      File "/usr/local/lib/python3.8/site-packages/authlib/integrations/base_client/base_app.py", line 126, in _retrieve_oauth2_access_token_params
        raise MismatchingStateError()
    authlib.integrations.base_client.errors.MismatchingStateError: mismatching_state: CSRF Warning! State not equal in request and response.
    

    To Reproduce

    A minimal example to reproduce the behavior: This is my code: In superset_config.py

    AUTH_USER_REGISTRATION = True
    AUTH_USER_REGISTRATION_ROLE = 'Public'
    CSRF_ENABLED = True
    ENABLE_PROXY_FIX = True
    OAUTH_PROVIDERS = [
        {
            'name': 'keycloak',
            'token_key': 'access_token',
            'icon': 'fa-icon',
            'remote_app': {
                'client_id': CLIENT_ID,
                'client_secret': CLIENT_SECRET,
                'client_kwargs': {
                    'scope': 'openid email profile'
                },
                'access_token_method': 'POST',
                'api_base_url': 'https://KEYCLOAK_URL/auth/realms/REALM_NAME/protocol/openid-connect/',
                'access_token_url': 'https://KEYCLOAK_URL/auth/realms/REALM_NAME/protocol/openid-connect/token',
                'authorize_url': 'https://KEYCLOAK_URL/auth/realms/REALM_NAME/protocol/openid-connect/auth',
            },
        }
    ]
    CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
    

    In extended_security.py

    
    
    class OIDCSecurityManager(SupersetSecurityManager):
        def get_oauth_user_info(self, provider, response=None):
            if provider == 'keycloak':
                me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo")
                return {
                    "first_name": me.data.get("given_name", ""),
                    "last_name": me.data.get("family_name", ""),
                    "email": me.data.get("email", "")
                }
    

    Expected behavior

    Suerpset redirect user to keycloak authentication site as expected. Upon finishing authenticating and getting redirected back to superset, CSRF Warning! State not equal in request and response occur.

    Environment: Docker:

    • Python Version: 3.8
    • Authlib Version: 0.15.4

    Additional context

    Tried on different browser (chrome, firefox, edge, etc.), clearing cookies, etc., but the error is still there. Would very appreciate some help.

  • PKCE client authentication in shared public/private server

    PKCE client authentication in shared public/private server

    This issue is similar to my other reported issue #115 but essentially in an authorization server where applications are both public and private it isn't possible to natively authenticate to the token server as a public and private client depending on type of authorization request.

    The only way to achieve this currently is to extend the client auth methods with a custom authentication scheme:

    def authenticate_rfc7636(query_client, request):
        data = request.form
        code_verifier = data.get('code_verifier')
        if code_verifier:
            return authenticate_none(query_client, request)
    

    This works because rfc7636 always runs validate_code_verifier after after_validate_token_request, which validates the code verifier provided.

  • Cyclic dependencies when using AuthorizationServer(Client, app)

    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.

  • Add Starlette client (#148)

    Add Starlette client (#148)

    This commit adds a new Starlette client along with documentation and tests. The Starlette client is suitable for use with frameworks that build on top of Starlette such as FastAPI and Bocadillo.

    This implements #148.

    What kind of change does this PR introduce? (check at least one)

    • [ ] Bugfix
    • [x] Feature
    • [ ] Code style update
    • [ ] Refactor
    • [ ] Other, please describe:

    Does this PR introduce a breaking change? (check one)

    • [ ] Yes
    • [x] No

    • [x] You consent that the copyright of your pull request source code belongs to Authlib's author.
  • Allow passing in additional jwt headers

    Allow passing in additional jwt headers

    What kind of change does this PR introduce? (check at least one)

    • [ ] Bugfix
    • [x] Feature
    • [ ] Code style update
    • [ ] Refactor
    • [ ] Other, please describe:

    Does this PR introduce a breaking change? (check one)

    • [ ] Yes
    • [x] No

    • [x] You consent that the copyright of your pull request source code belongs to Authlib's author.

    Referencing https://github.com/lepture/authlib/issues/391

    I'd like to pass in addtional JWT headers into the client assertion, such as the kid so the recipient of the token can find the corresponding key at the registered JWKS endpoint.

    Describe the solution you'd like

    When setting up an PrivateKeyJWT() instance to register as client auth method, I'd like to pass in additional JWT headers such as the kid.

  • Twitter OAuth Not working due to missing 'request_token' parameter

    Twitter OAuth Not working due to missing 'request_token' parameter

    Describe the bug

    oauth.twitter.authorize_access_token(request) is normally called in the callback function. The request object usually has a request_token parameter in its query parameters. However, Twitter's 3-legged OAuth process, described here: https://developer.twitter.com/en/docs/authentication/oauth-1-0a/obtaining-user-access-tokens

    Does not return a request_token parameter. Instead it returns oauth_token and oauth_verifier. Both of these are expected to be passed back to the twitter's auth endpoint to get the user's tokens.

    The problem comes in function fetch_access_token in authlib\integrations\base_client\async_app.py. That function contains the following code:

                    if request_token is None:
                        raise MissingRequestTokenError()
                    # merge request token with verifier
                    token = {}
                    token.update(request_token)
                    token.update(params)
                    client.token = token
    

    Since twitter doesn't have a request_token, the MissingRequestTokenError() is raised and the authorize process fails. This is an error for twitter because twitter doesn't require a request_token. I commented out the lines 1, 2, and 5 from above, and then twitter oauth worked fine, returning the client's token as expected.

    FWIW, I use loginpass but this defines the twitter metadata the same as is shown in the examples, so I don't think that's the source of the error.

    Is there a way to specify that for twitter, this function should just skip the request_token parameter? More generally, is there a way to specify what query parameters are expected to be returned for each endpoint, so that in the future, other endpoints that return non-standard parameters can be accommodated?

    Error Stacks

    ERROR:    Exception in ASGI application
    Traceback (most recent call last):
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 389, in run_asgi
        result = await app(self.scope, self.receive, self.send)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 45, in __call__
        return await self.app(scope, receive, send)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\fastapi\applications.py", line 179, in __call__
        await super().__call__(scope, receive, send)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\applications.py", line 111, in __call__
        await self.middleware_stack(scope, receive, send)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\middleware\errors.py", line 181, in __call__
        raise exc from None
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
        await self.app(scope, receive, _send)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\middleware\cors.py", line 78, in __call__
        await self.app(scope, receive, send)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\middleware\sessions.py", line 75, in __call__
        await self.app(scope, receive, send_wrapper)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\exceptions.py", line 82, in __call__
        raise exc from None
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\exceptions.py", line 71, in __call__
        await self.app(scope, receive, sender)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\routing.py", line 566, in __call__
        await route.handle(scope, receive, send)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\routing.py", line 227, in handle
        await self.app(scope, receive, send)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\starlette\routing.py", line 41, in app
        response = await func(request)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\fastapi\routing.py", line 182, in app
        raw_response = await run_endpoint_function(
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\fastapi\routing.py", line 133, in run_endpoint_function
        return await dependant.call(**values)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\app\api\api_v1\endpoints\link_accounts.py", line 156, in auth_twitter
        token = await oauth.twitter.authorize_access_token(request)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\authlib\integrations\starlette_client\integration.py", line 64, in authorize_access_token
        return await self.fetch_access_token(**params)
      File "C:\Users\mraju\PycharmProjects\api-server-useraccounts\venv\lib\site-packages\authlib\integrations\base_client\async_app.py", line 90, in fetch_access_token
        raise MissingRequestTokenError()
    authlib.integrations.base_client.errors.MissingRequestTokenError: missing_request_token: 
    

    To Reproduce

    @router.get('/twitter/login')
    async def login_twitter(request: Request) -> Any:
        redirect_uri = request.url_for('auth_twitter')
        return await oauth.twitter.authorize_redirect(request, redirect_uri)
    
    
    @router.get('/twitter/callback')
    async def auth_twitter(request: Request):
        token = await oauth.twitter.authorize_access_token(request)
    

    Expected behavior

    A clear and concise description of what you expected to happen.

    Environment:

    • OS: Windows
    • Python Version: 3.9.0
    • Authlib Version: 0.15.2

    Additional context

    Add any other context about the problem here.

  • Same input causes exception in python 3.8.3 but not in python 3.6.8

    Same input causes exception in python 3.8.3 but not in python 3.6.8

    Describe the bug

    An input for authlib.jose.jwt.decode() causes an exception on python 3.8.3 while it works on python 3.6.8 .

    I'm using:

    from authlib.jose import jwt
    jwt.decode(mytoken, key=mykey, claims_options=something)
    

    The causing parameter is key. In my case, the (shortened) value is :

    {
      "keys": [
        {
          "kid": "something2",
          "kty": "RSA",
          "alg": "RS256",
          "use": "sig",
          "n": "something3",
          "e": "AQAB",
          "x5c": ["something4"],
          "x5t": "something5",
          "x5t#S256": "something6"
        }
      ]
    }
    

    However, if I set mykey to the key in the inner list:

    {
          "kid": "something2",
          "kty": "RSA",
          "alg": "RS256",
          "use": "sig",
          "n": "something3",
          "e": "AQAB",
          "x5c": ["something4"],
          "x5t": "something5",
          "x5t#S256": "something6"
        }
    

    then authlib accepts the input for both, python 3.6.8 and 3.8.3.

    Error Stacks

    [...]
      File "/Users/njjn/.pyenv/versions/demo2/lib/python3.8/site-packages/authlib/jose/rfc7519/jwt.py", line 98, in decode
        data = self._jws.deserialize_compact(s, load_key, decode_payload)
      File "/Users/njjn/.pyenv/versions/demo2/lib/python3.8/site-packages/authlib/jose/rfc7515/jws.py", line 102, in deserialize_compact
        algorithm, key = self._prepare_algorithm_key(jws_header, payload, key)
      File "/Users/njjn/.pyenv/versions/demo2/lib/python3.8/site-packages/authlib/jose/rfc7515/jws.py", line 258, in _prepare_algorithm_key
        key = algorithm.prepare_key(key)
      File "/Users/njjn/.pyenv/versions/demo2/lib/python3.8/site-packages/authlib/jose/rfc7518/_cryptography_backends/_jws.py", line 41, in prepare_key
        return RSAKey.import_key(raw_data)
      File "/Users/njjn/.pyenv/versions/demo2/lib/python3.8/site-packages/authlib/jose/rfc7518/_cryptography_backends/_keys.py", line 116, in import_key
        return import_key(
      File "/Users/njjn/.pyenv/versions/demo2/lib/python3.8/site-packages/authlib/jose/rfc7518/_cryptography_backends/_keys.py", line 277, in import_key
        cls.check_required_fields(raw)
      File "/Users/njjn/.pyenv/versions/demo2/lib/python3.8/site-packages/authlib/jose/rfc7517/models.py", line 120, in check_required_fields
        raise ValueError('Missing required field: "{}"'.format(k))
    ValueError: Missing required field: "e"
    

    To Reproduce

    (untested): get a valid key and put it in the structure as depicted in my example above.

    Expected behavior

    The same input gets accepted without errors for both python versions.

    Environment:

    • OS: macos/linux
    • Python Version: 3.8.3
    • Authlib Version: v0.15
  • error w/ google oauth

    error w/ google oauth

    Hello, I can't made a successful auth against google oauth because an error (that seems related or identical to https://github.com/authlib/loginpass/issues/47).

    Here is the relevant code:

    config = Config(".env")  # pylint: disable=invalid-name
    oauth = OAuth(config)
    
    CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
    oauth.register(
        name='google',
        server_metadata_url=CONF_URL,
        client_kwargs={
            'scope': 'openid email profile'
        }
    )
    
    async def login(request):
        redirect_uri = request.url_for('auth')
        return await oauth.google.authorize_redirect(request, redirect_uri)
    
    async def auth(request):
        logging.critical(request.headers)
        token = await oauth.google.authorize_access_token(request)
        user = await oauth.google.parse_id_token(request, token)
        request.session['user'] = dict(user)
        return RedirectResponse(url='/')
    
    ROUTES = [
        Route('/login', endpoint=login),
        Route('/auth', endpoint=auth),
    ]
    
    app = Starlette(debug=True, routes=ROUTES)  # pylint: disable=invalid-name
    

    when /auth endpoint is hit after a successful authentication, I get this traceback (first line is mine):

    [osso] CRITICAL: Headers({'host': 'osso.dev.scimmia.net', 'x-request-id': '299db1f2a6d8f187459083ed590d4adb', 'x-real-ip': '10.42.0.1', 'x-forwarded-for': '10.42.0.1', 'x-forwarded-host': 'osso.dev.scimmia.net', 'x-forwarded-port': '443', 'x-forwarded-proto': 'https', 'x-scheme': 'https', 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'accept-language': 'en-US,en;q=0.5', 'accept-encoding': 'gzip, deflate, br', 'referer': 'https://accounts.google.com/', 'dnt': '1', 'upgrade-insecure-requests': '1'})
    [osso] INFO:     10.42.0.1:0 - "GET /auth?state=VNyCSjjCXSojWBTqNjsm3XlxBSJEgg&code=4%2FwwEnVamW5jzr4hOz4agsa1kjrKBlCOrMJTdO0pbFM9nq9qDbJ7-riJQ4Sh9TeokDyvz47FnRtY4DQsMMNH3N_II&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=consent HTTP/1.1" 500 Internal Server Error
    [osso] ERROR:    Exception in ASGI application
    [osso] Traceback (most recent call last):
    [osso]   File "/usr/local/lib/python3.7/site-packages/uvicorn/protocols/http/httptools_impl.py", line 385, in run_asgi
    [osso]     result = await app(self.scope, self.receive, self.send)
    [osso]   File "/usr/local/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    [osso]     return await self.app(scope, receive, send)
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette/applications.py", line 102, in __call__
    [osso]     await self.middleware_stack(scope, receive, send)
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
    [osso]     raise exc from None
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
    [osso]     await self.app(scope, receive, _send)
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette_authlib/middleware.py", line 122, in __call__
    [osso]     await self.app(scope, receive, send_wrapper)
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
    [osso]     raise exc from None
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
    [osso]     await self.app(scope, receive, sender)
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette/routing.py", line 550, in __call__
    [osso]     await route.handle(scope, receive, send)
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
    [osso]     await self.app(scope, receive, send)
    [osso]   File "/usr/local/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
    [osso]     response = await func(request)
    [osso]   File "./osso/app.py", line 79, in auth
    [osso]     token = await oauth.google.authorize_access_token(request)
    [osso]   File "/usr/local/lib/python3.7/site-packages/authlib/integrations/starlette_client/integration.py", line 58, in authorize_access_token
    [osso]     return await self.fetch_access_token(**params)
    [osso]   File "/usr/local/lib/python3.7/site-packages/authlib/integrations/base_client/async_app.py", line 105, in fetch_access_token
    [osso]     token = await client.fetch_token(token_endpoint, **kwargs)
    [osso]   File "/usr/local/lib/python3.7/site-packages/authlib/integrations/httpx_client/oauth2_client.py", line 120, in _fetch_token
    [osso]     return self.parse_response_token(resp.json())
    [osso]   File "/usr/local/lib/python3.7/site-packages/authlib/oauth2/client.py", line 348, in parse_response_token
    [osso]     self.handle_error(error, description)
    [osso]   File "/usr/local/lib/python3.7/site-packages/authlib/integrations/httpx_client/oauth2_client.py", line 76, in handle_error
    [osso]     raise OAuthError(error_type, error_description)
    [osso] authlib.common.errors.AuthlibBaseError: invalid_request: Missing parameter: redirect_uri
    

    Expected behavior

    A successful login via google oauth

    Environment:

    • OS: python:3.7-slim docker image (alpine)
    • Authlib Version: 0.14.1

    Thank you, ciao!

  • More flexibility for session objects

    More flexibility for session objects

    What kind of change does this PR introduce? (check at least one)

    • [ ] Bugfix
    • [x] Feature
    • [ ] Code style update
    • [x] Refactor

    Does this PR introduce a breaking change? (check one)

    • [ ] Yes
    • [x] No

    • [x] You consent that the copyright of your pull request source code belongs to Authlib's author.

    Currently, OAuth2Session inherits from requests.Session and uses that session for communicating with both authorization server and resource server. This has the following problems:

    1. HTTP clients other than requests.Session are not supported, even if they implement the same interface. Consider Starlette Test Client, aiohttp.ClientSession as examples.

    2. Sometimes, an initialized/configured HTTP session is only available as instantiated object. It's impossible to use this from OAuth2Session because the latter necessarily creates a new session.

    3. Two separate sessions for communicating with authorization server and resource server cannot be used, although this would be highly desired in some work-flows (e.g. unit-testing a resource server).

    This PR introduces intermediate class OAuth2Handler holding all complicated OAuth 2.0 client logic. Rather than inherit from requests.Session, OAuth2Handler encapsulates the provided requests.Session-like object.

    OAuth2Session implements both OAuth2Handler and requests.Session, as before.

    New slim OAuth2HandlerAuth encapsulates OAuth2Handler and implements requests.auth.AuthBase with auto refresh token feature.

    OAuth2Handler can be used with any requests.Session-like objects. OAuth2HandlerAuth can be used with any requests.Session object, regardless of which HTTP client session is used in its OAuth2Handler (separate communication channels for authorization server and resource server).

    Please review and comment.

  • PEP-484 Type Hints

    PEP-484 Type Hints

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

    We use python with strict typing at our organization which means we have to add a bunch of "type: ignore" comments wherever this library is used. Adding types would make working with this library much easier and require much less context switching between the docs and the editor thanks to autocomplete.

    Describe the solution you'd like

    I understand a bunch of functionality in this library is very hard to type (dynamic clients, etc.), so maybe this effort could start with the JOSE module.

    Describe alternatives you've considered

    Alternatively, type stubs for this library could be added to the typeshed project but that is not ideal as it would be very easy for those to get out of sync.

  • Provide a UserInfo endpoint implementation

    Provide a UserInfo endpoint implementation

    I've succesfully implemented an OAuth2/OIDC provider using authlib. But now stumbled upon a issue where a clients is not able to actually use the id_token (in addition to the access token, it can only use one token at a time). This shouldn't be an issue, as there's always the userinfo endpoint that can be read using the access token (or so I tought). I looked into adding the userinfo endpoint, but cannot find a implementation in authlib.

    Describe the solution you'd like

    I'd like there to be a (reference) implementation of the userinfo endpoint as described in section 5.3 of the OpenID Connect Core 1.0 spec.

  • header kwarg removed from 1.0.0, present in 0.15.5

    header kwarg removed from 1.0.0, present in 0.15.5

    Describe the bug

    In 0.15.5, the initializer and sign method for class ClientSecretJWT looked like this:

        def __init__(self, token_endpoint=None, claims=None, header=None):
            self.token_endpoint = token_endpoint
            self.claims = claims
            self.header = header
    
        def sign(self, auth, token_endpoint):
            return client_secret_jwt_sign(
                auth.client_secret,
                client_id=auth.client_id,
                token_endpoint=token_endpoint,
                claims=self.claims,
                header=self.header,
                alg=self.alg,
            )
    

    See here: https://github.com/lepture/authlib/blob/v0.15.5/authlib/oauth2/rfc7523/auth.py#L30

    In 1.0.0+, the same looks like this:

        def __init__(self, token_endpoint=None, claims=None, alg=None):
            self.token_endpoint = token_endpoint
            self.claims = claims
            if alg is not None:
                self.alg = alg
    
        def sign(self, auth, token_endpoint):
            return client_secret_jwt_sign(
                auth.client_secret,
                client_id=auth.client_id,
                token_endpoint=token_endpoint,
                claims=self.claims,
                alg=self.alg,
            )
    

    See here: https://github.com/lepture/authlib/blob/v1.0.1/authlib/oauth2/rfc7523/auth.py#30

    The header kwarg has disappeared. I was using that for my application, and now it's no longer possible to use that.

    Are the 1.0.0 and 0.15.x trees unrelated? They don't look like the share the same history. Is there documentation on what happened here?

    Error Stacks

    "__init__() got an unexpected keyword argument 'header'"
    

    To Reproduce

    N/A

    Expected behavior

    I expected the class ClientSecretJWT to retain the header kwarg and have it be possible to use it.

    Environment:

    • OS: Ubuntu 20.04.4 LTS
    • Python Version: 3.9
    • Authlib Version: 1.0.1
  • Two tests/jose/test_jwe.py tests failing

    Two tests/jose/test_jwe.py tests failing

    While packaging this package for openSUSE we try to start running the testsuite during the packaging (so that we may catch some unexpected failure to build package correctly) and when running tests/jose I got this:

    [   11s] + python3.9 -mpytest tests/jose
    [   11s] ============================= test session starts ==============================
    [   11s] platform linux -- Python 3.9.12, pytest-7.1.1, pluggy-1.0.0
    [   11s] rootdir: /home/abuild/rpmbuild/BUILD/authlib-1.0.1, configfile: tox.ini
    [   11s] plugins: anyio-3.5.0, asyncio-0.17.2
    [   11s] asyncio: mode=auto
    [   11s] collected 134 items
    [   11s]
    [   11s] tests/jose/test_jwe.py ..................F.............................. [ 36%]
    [   11s] ............................F                                            [ 58%]
    [   12s] tests/jose/test_jwk.py .......................                           [ 75%]
    [   12s] tests/jose/test_jws.py ................                                  [ 87%]
    [   12s] tests/jose/test_jwt.py .................                                 [100%]
    [   12s]
    [   12s] =================================== FAILURES ===================================
    [   12s] __________________________ JWETest.test_dir_alg_xc20p __________________________
    [   12s]
    [   12s] self = <tests.jose.test_jwe.JWETest testMethod=test_dir_alg_xc20p>
    [   12s]
    [   12s]     def test_dir_alg_xc20p(self):
    [   12s]         jwe = JsonWebEncryption()
    [   12s]         key = OctKey.generate_key(256, is_private=True)
    [   12s]         protected = {'alg': 'dir', 'enc': 'XC20P'}
    [   12s] >       data = jwe.serialize_compact(protected, b'hello', key)
    [   12s]
    [   12s] tests/jose/test_jwe.py:2657:
    [   12s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    [   12s] authlib/jose/rfc7516/jwe.py:80: in serialize_compact
    [   12s]     enc = self.get_header_enc(protected)
    [   12s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    [   12s]
    [   12s] self = <authlib.jose.rfc7516.jwe.JsonWebEncryption object at 0x7fad22f4ac40>
    [   12s] header = {'alg': 'dir', 'enc': 'XC20P'}
    [   12s]
    [   12s]     def get_header_enc(self, header):
    [   12s]         if 'enc' not in header:
    [   12s]             raise MissingEncryptionAlgorithmError()
    [   12s]         enc = header['enc']
    [   12s]         if self._algorithms and enc not in self._algorithms:
    [   12s]             raise UnsupportedEncryptionAlgorithmError()
    [   12s]         if enc not in self.ENC_REGISTRY:
    [   12s] >           raise UnsupportedEncryptionAlgorithmError()
    [   12s] E           authlib.jose.errors.UnsupportedEncryptionAlgorithmError: unsupported_encryption_algorithm: Unsupported "enc" value in header
    [   12s]
    [   12s] authlib/jose/rfc7516/jwe.py:678: UnsupportedEncryptionAlgorithmError
    [   12s] _______________ JWETest.test_xc20p_content_encryption_decryption _______________
    [   12s]
    [   12s] self = <tests.jose.test_jwe.JWETest testMethod=test_xc20p_content_encryption_decryption>
    [   12s]
    [   12s]     def test_xc20p_content_encryption_decryption(self):
    [   12s]         # https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03#appendix-A.3.1
    [   12s] >       enc = JsonWebEncryption.ENC_REGISTRY['XC20P']
    [   12s] E       KeyError: 'XC20P'
    [   12s]
    [   12s] tests/jose/test_jwe.py:2672: KeyError
    [   12s] =========================== short test summary info ============================
    [   12s] FAILED tests/jose/test_jwe.py::JWETest::test_dir_alg_xc20p - authlib.jose.err...
    [   12s] FAILED tests/jose/test_jwe.py::JWETest::test_xc20p_content_encryption_decryption
    [   12s] ======================== 2 failed, 132 passed in 1.08s =========================
    

    Complete build log with all details of packages used and steps taken to reproduce.

    • OS: openSUSE/Tumbleweed as of 2022-05-10
    • Python Version: various versions of Python, this traceback is from 3.9.12, pytest-7.1.1, and pluggy-1.0.0
    • Authlib Version: 1.0.1
  • fetch_access_token() no longer raises OAuthError but HTTPError instead.

    fetch_access_token() no longer raises OAuthError but HTTPError instead.

    Describe the bug

    A change introduced in 1.0.0 makes fetch_access_token() return a requests.exceptions.HTTPError instead of an OAuthError.
    Tested in Flask integration.

    To Reproduce

    Run code equivalent to this in a working Flask config.

    try:
        token = oauth.myOauth2.fetch_access_token(
            username=request.form.get('username'),
            password=request.form.get('password')
        )
    except OAuthError as e:
        if e.description:
            flash(e.description)
            return render_template('login.html')
        raise
    

    Both valid and invalid user/pwd works as expected in 0.15.5. Valid login works in 1.0.0, but invalid raises an HTTPError. The error response contains what looks like the expected json: {'error': 'invalid_grant', 'error_description': 'INVALID_CREDENTIALS'}

    Expected behavior

    An OAuthError exception is raised on invalid login.

    Environment:

    • OS: Windows Server 2016
    • Python Version: 3.8.1
    • Authlib Version: 0.15.5 / 1.0.0

    Additional context

    All other packages are up to date pt. I was advised that this is the correct way to do password grant flow.

A fully tested, abstract interface to creating OAuth clients and servers.

Note: This library implements OAuth 1.0 and not OAuth 2.0. Overview python-oauth2 is a python oauth library fully compatible with python versions: 2.6

May 12, 2022
Django-react-firebase-auth - A web app showcasing OAuth2.0 + OpenID Connect using Firebase, Django-Rest-Framework and React
Django-react-firebase-auth - A web app showcasing OAuth2.0 + OpenID Connect using Firebase, Django-Rest-Framework and React

Demo app to show Django Rest Framework working with Firebase for authentication

Feb 22, 2022
Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster
Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster

Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster. Kubernetes supports OpenID Connect Tokens as a way to identify users who access the cluster. Kube OpenID Connect helps users with it's plugin to authenticate an get kubectl config.

May 2, 2022
An open source Flask extension that provides JWT support (with batteries included)!

Flask-JWT-Extended Features Flask-JWT-Extended not only adds support for using JSON Web Tokens (JWT) to Flask for protecting views, but also many help

May 13, 2022
Connect-4-AI - AI that plays Connect-4 using the minimax algorithm

Connect-4-AI Brief overview I coded up the Connect-4 (or four-in-a-row) game in

Feb 15, 2022
Boilerplate/Starter Project for building RESTful APIs using Flask, SQLite, JWT authentication.

auth-phyton Boilerplate/Starter Project for building RESTful APIs using Flask, SQLite, JWT authentication. Setup Step #1 - Install dependencies $ pip

Dec 26, 2021
A Python library for OAuth 1.0/a, 2.0, and Ofly.

Rauth A simple Python OAuth 1.0/a, OAuth 2.0, and Ofly consumer library built on top of Requests. Features Supports OAuth 1.0/a, 2.0 and Ofly Service

May 11, 2022
This is a Python library for accessing resources protected by OAuth 2.0.

This is a client library for accessing resources protected by OAuth 2.0. Note: oauth2client is now deprecated. No more features will be added to the l

May 14, 2022
Python library for generating a Mastercard API compliant OAuth signature.
Python library for generating a Mastercard API compliant OAuth signature.

oauth1-signer-python Table of Contents Overview Compatibility References Usage Prerequisites Adding the Library to Your Project Importing the Code Loa

May 5, 2022
Flask JWT Router is a Python library that adds authorised routes to a Flask app.
Flask JWT Router is a Python library that adds authorised routes to a Flask app.

Read the docs: Flask-JWT-Router Flask JWT Router Flask JWT Router is a Python library that adds authorised routes to a Flask app. Both basic & Google'

Mar 26, 2022
Toolkit for Pyramid, a Pylons Project, to add Authentication and Authorization using Velruse (OAuth) and/or a local database, CSRF, ReCaptcha, Sessions, Flash messages and I18N

Apex Authentication, Form Library, I18N/L10N, Flash Message Template (not associated with Pyramid, a Pylons project) Uses alchemy Authentication Authe

Feb 16, 2022
Doing the OAuth dance with style using Flask, requests, and oauthlib.

Flask-Dance Doing the OAuth dance with style using Flask, requests, and oauthlib. Currently, only OAuth consumers are supported, but this project coul

May 19, 2022
Doing the OAuth dance with style using Flask, requests, and oauthlib.

Flask-Dance Doing the OAuth dance with style using Flask, requests, and oauthlib. Currently, only OAuth consumers are supported, but this project coul

Feb 17, 2021
Doing the OAuth dance with style using Flask, requests, and oauthlib.

Flask-Dance Doing the OAuth dance with style using Flask, requests, and oauthlib. Currently, only OAuth consumers are supported, but this project coul

Feb 22, 2021
A generic, spec-compliant, thorough implementation of the OAuth request-signing logic
A generic, spec-compliant, thorough implementation of the OAuth request-signing logic

OAuthLib - Python Framework for OAuth1 & OAuth2 *A generic, spec-compliant, thorough implementation of the OAuth request-signing logic for Python 3.5+

May 20, 2022
A generic, spec-compliant, thorough implementation of the OAuth request-signing logic
A generic, spec-compliant, thorough implementation of the OAuth request-signing logic

OAuthLib - Python Framework for OAuth1 & OAuth2 *A generic, spec-compliant, thorough implementation of the OAuth request-signing logic for Python 3.5+

May 19, 2022
Phishing Abusing Microsoft 365 OAuth Authorization Flow
Phishing Abusing Microsoft 365 OAuth Authorization Flow

Microsoft365_devicePhish Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack This is a simple proof-of-concept script that allows an at

May 10, 2022
Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack
Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack

Microsoft365_devicePhish Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack This is a simple proof-of-concept script that allows an at

May 3, 2022
Local server that gives you your OAuth 2.0 tokens needed to interact with the Conta Azul's API

What's this? This is a django project meant to be run locally that gives you your OAuth 2.0 tokens needed to interact with Conta Azul's API Prerequisi

Apr 13, 2022