Authlib: Python Authentication

Release v1.1.0. (Installation)

The ultimate Python library in building OAuth and OpenID Connect servers. It is designed from low level specifications implementations to high level frameworks integrations, to meet the needs of everyone.

Authlib is compatible with Python3.6+.

User’s Guide

This part of the documentation begins with some background information about Authlib, and installation of Authlib. Then it will explain OAuth 1.0, OAuth 2.0, and JOSE. At last, it shows the implementation in frameworks, and libraries such as Flask, Django, Requests, HTTPX, Starlette, FastAPI, and etc.

Get Started

This part of the documentation begins with some background information about Authlib, and installation of Authlib.

Introduction

Authlib is the ultimate Python library in building OAuth and OpenID Connect clients and servers. It offers generic implementations of RFCs, including OAuth 1.0, OAuth 2.0, JWT and many more. It becomes a Monolithic project that powers from low-level specification implementation to high-level framework integrations.

I’m intended to make it profitable so that it can be Sustainable, check out the Funding section.

Monolithic

Authlib is a monolithic library. While being monolithic, it keeps everything synchronized, from spec implementation to framework integrations, from client requests to service providers.

The benefits are obvious; it won’t break things. When specifications changed, implementation will change too. Let the developers of Authlib take the pain, users of Authlib should not suffer from it.

You don’t have to worry about monolithic, it doesn’t cost your memory. If you don’t import a module, it won’t be loaded. We don’t madly import everything into the root __init__.py.

Extendable

Authlib is designed as flexible as possible. Since it is built from low-level specification implementation to high-level framework integrations, if a high level can’t meet your needs, you can always create one for your purpose based on the low-level implementation.

Most of the cases, you don’t need to do so. Extendable has been taken into account from the start of the project. Take OAuth 2.0 server as an example, instead of a pre-configured server, Authlib takes advantage of register.

authorization_server.register_grant(AuthorizationCodeGrant)
authorization_server.register_endpoint(RevocationEndpoint)

If you find anything not that extendable, you can ask help on StackOverflow or open an issue on GitHub.

Credits

This project is inspired by:

  • OAuthLib

  • Flask-OAuthlib

  • requests-oauthlib

  • pyjwt

Installation

This part of the documentation covers the installation of Authlib, just like any other software package needs to be installed first.

$ pip install Authlib

Installing Authlib is simple with pip:

$ pip install Authlib

It will also install the dependencies:

  • cryptography

Note

You may enter problems when installing cryptography, check its official document at https://cryptography.io/en/latest/installation/

Using Authlib with requests:

$ pip install Authlib requests

Using Authlib with httpx:

$ pip install Authlib httpx

Using Authlib with Flask:

$ pip install Authlib Flask

Using Authlib with Django:

$ pip install Authlib Django

Using Authlib with Starlette:

$ pip install Authlib httpx Starlette

Changed in version v0.12: “requests” is an optional dependency since v0.12. If you want to use Authlib client, you have to install “requests” by yourself:

$ pip install Authlib requests
Get the Source Code

Authlib is actively developed on GitHub, where the code is always available.

You can either clone the public repository:

$ git clone git://github.com/lepture/authlib.git

Download the tarball:

$ curl -OL https://github.com/lepture/authlib/tarball/master

Or, download the zipball:

$ curl -OL https://github.com/lepture/authlib/zipball/master

Once you have a copy of the source, you can embed it in your Python package, or install it into your site-packages easily:

$ cd authlib
$ pip install .

Logging

You can always enable debug logging when you run into issues in your code:

import logging
import sys
log = logging.getLogger('authlib')
log.addHandler(logging.StreamHandler(sys.stdout))
log.setLevel(logging.DEBUG)

We are still designing the logging system. (TBD)

OAuth Clients

This part of the documentation contains information on the client parts. Authlib provides many frameworks integrations, including:

  • The famous Python Requests

  • A next generation HTTP client for Python: httpx

  • Flask web framework integration

  • Django web framework integration

  • Starlette web framework integration

  • FastAPI web framework integration

In order to use Authlib client, you have to install each library yourself. For example, you want to use requests OAuth clients:

$ pip install Authlib requests

For instance, you want to use httpx OAuth clients:

$ pip install -U Authlib httpx

Here is a simple overview of Flask OAuth client:

from flask import Flask, jsonify
from authlib.integrations.flask_client import OAuth

app = Flask(__name__)
oauth = OAuth(app)
github = oauth.register('github', {...})

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

@app.route('/authorize')
def authorize():
    token = github.authorize_access_token()
    # you can save the token into database
    profile = github.get('/user', token=token)
    return jsonify(profile)

Follow the documentation below to find out more in detail.

OAuth 1 Session

This documentation covers the common design of a Python OAuth 1.0 client. Authlib provides three implementations of OAuth 1.0 client:

  1. requests_client.OAuth1Session implementation of OAuth for Requests, which is a replacement for requests-oauthlib.

  2. httpx_client.AsyncOAuth1Client implementation of OAuth for HTTPX, which is an async OAuth 1.0 client.

requests_client.OAuth1Session and httpx_client.AsyncOAuth1Client shares the same API.

There are also frameworks integrations of Flask OAuth Client, Django OAuth Client and Starlette OAuth Client. If you are using these frameworks, you may have interests in their own documentation.

If you are not familiar with OAuth 1.0, it is better to read Introduce OAuth 1.0 now.

Initialize OAuth 1.0 Client

There are three steps in OAuth 1 to obtain an access token:

  1. fetch a temporary credential

  2. visit the authorization page

  3. exchange access token with the temporary credential

But first, we need to initialize an OAuth 1.0 client:

>>> client_id = 'Your Twitter client key'
>>> client_secret = 'Your Twitter client secret'
>>> # using requests client
>>> from authlib.integrations.requests_client import OAuth1Session
>>> client = OAuth1Session(client_id, client_secret)
>>> # using httpx client
>>> from authlib.integrations.httpx_client import AsyncOAuth1Client
>>> client = AsyncOAuth1Client(client_id, client_secret)
Fetch Temporary Credential

The first step is to fetch temporary credential, which will be used to generate authorization URL:

>>> request_token_url = 'https://api.twitter.com/oauth/request_token'
>>> request_token = client.fetch_request_token(request_token_url)
>>> print(request_token)
{'oauth_token': 'gA..H', 'oauth_token_secret': 'lp..X', 'oauth_callback_confirmed': 'true'}

Save this temporary credential for later use (if required).

You can assign a redirect_uri before fetching the request token, if you want to redirect back to another URL other than the one you registered:

>>> client.redirect_uri = 'https://your-domain.org/auth'
>>> client.fetch_request_token(request_token_url)
Redirect to Authorization Endpoint

The second step is to generate the authorization URL:

>>> authenticate_url = 'https://api.twitter.com/oauth/authenticate'
>>> client.create_authorization_url(authenticate_url, request_token['oauth_token'])
'https://api.twitter.com/oauth/authenticate?oauth_token=gA..H'

Actually, the second parameter request_token can be omitted, since session is re-used:

>>> client.create_authorization_url(authenticate_url)

Now visit the authorization url that create_authorization_url generated, and grant the authorization.

Fetch Access Token

When the authorization is granted, you will be redirected back to your registered callback URI. For instance:

https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq

If you assigned redirect_uri in Fetch Access Token, the authorize response would be something like:

https://your-domain.org/auth?oauth_token=gA..H&oauth_verifier=fcg..1Dq

Now fetch the access token with this response:

>>> resp_url = 'https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq'
>>> client.parse_authorization_response(resp_url)
>>> access_token_url = 'https://api.twitter.com/oauth/access_token'
>>> token = client.fetch_access_token(access_token_url)
>>> print(token)
{
    'oauth_token': '12345-st..E',
    'oauth_token_secret': 'o67..X',
    'user_id': '12345',
    'screen_name': 'lepture',
    'x_auth_expires': '0'
}
>>> save_access_token(token)

Save this token to access protected resources.

The above flow is not always what we will use in a real project. When we are redirected to authorization endpoint, our session is over. In this case, when the authorization server send us back to our server, we need to create another session:

>>> # restore your saved request token, which is a dict
>>> request_token = restore_request_token()
>>> oauth_token = request_token['oauth_token']
>>> oauth_token_secret = request_token['oauth_token_secret']
>>> from authlib.integrations.requests_client import OAuth1Session
>>> # if using httpx: from authlib.integrations.httpx_client import AsyncOAuth1Client
>>> client = OAuth1Session(
...     client_id, client_secret,
...     token=oauth_token,
...     token_secret=oauth_token_secret)
>>> # there is no need for `parse_authorization_response` if you can get `verifier`
>>> verifier = request.args.get('verifier')
>>> access_token_url = 'https://api.twitter.com/oauth/access_token'
>>> token = client.fetch_access_token(access_token_url, verifier)
Access Protected Resources

Now you can access the protected resources. If you re-use the session, you don’t need to do anything:

>>> account_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
>>> resp = client.get(account_url)
<Response [200]>
>>> resp.json()
{...}

The above is not the real flow, just like what we did in Fetch Access Token, we need to create another session ourselves:

>>> access_token = restore_access_token_from_database()
>>> oauth_token = access_token['oauth_token']
>>> oauth_token_secret = access_token['oauth_token_secret']
>>> # if using httpx: from authlib.integrations.httpx_client import AsyncOAuth1Client
>>> client = OAuth1Session(
...     client_id, client_secret,
...     token=oauth_token,
...     token_secret=oauth_token_secret)
>>> account_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
>>> resp = client.get(account_url)

Please note, there are duplicated steps in the documentation, read carefully and ignore the duplicated explains.

Using OAuth1Auth

It is also possible to access protected resources with OAuth1Auth object. Create an instance of OAuth1Auth with an access token:

# if using requests
from authlib.integrations.requests_client import OAuth1Auth

# if using httpx
from authlib.integrations.httpx_client import OAuth1Auth

auth = OAuth1Auth(
    client_id='..',
    client_secret=client_secret='..',
    token='oauth_token value',
    token_secret='oauth_token_secret value',
    ...
)

If using requests, pass this auth to access protected resources:

import requests

url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
resp = requests.get(url, auth=auth)

If using httpx, pass this auth to access protected resources:

import httpx

url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
resp = await httpx.get(url, auth=auth)

OAuth 2 Session

Changed in version v0.13: All client related code have been moved into authlib.integrations. For earlier versions of Authlib, check out their own versions documentation.

This documentation covers the common design of a Python OAuth 2.0 client. Authlib provides three implementations of OAuth 2.0 client:

  1. requests_client.OAuth2Session implementation of OAuth for Requests, which is a replacement for requests-oauthlib.

  2. httpx_client.AsyncOAuth2Client implementation of OAuth for HTTPX, which is async OAuth 2.0 client powered by HTTPX.

requests_client.OAuth2Session and httpx_client.AsyncOAuth2Client shares the same API.

There are also frameworks integrations of Flask OAuth Client, Django OAuth Client and Starlette OAuth Client. If you are using these frameworks, you may have interests in their own documentation.

If you are not familiar with OAuth 2.0, it is better to read Introduce OAuth 2.0 now.

OAuth2Session for Authorization Code

There are two steps in OAuth 2 to obtain an access token with authorization code grant type. Initialize the session for reuse:

>>> client_id = 'Your GitHub client ID'
>>> client_secret = 'Your GitHub client secret'
>>> scope = 'user:email'  # we want to fetch user's email
>>>
>>> # using requests implementation
>>> from authlib.integrations.requests_client import OAuth2Session
>>> client = OAuth2Session(client_id, client_secret, scope=scope)
>>>
>>> # using httpx implementation
>>> from authlib.integrations.httpx_client import AsyncOAuth2Client
>>> client = AsyncOAuth2Client(client_id, client_secret, scope=scope)

You can assign a redirect_uri in case you want to specify the callback url.

Redirect to Authorization Endpoint

Unlike OAuth 1, there is no request token. The first step is to jump to the remote authorization server:

>>> authorization_endpoint = 'https://github.com/login/oauth/authorize'
>>> uri, state = client.create_authorization_url(authorization_endpoint)
>>> print(uri)
https://github.com/login/oauth/authorize?response_type=code&client_id=c..id&scope=user%3Aemail&state=d..t

The create_authorization_url returns a tuple of (uri, state), in real project, you should save the state for later use.

Now head over to the generated authorization url, and grant the authorization.

Fetch Token

The authorization server will redirect you back to your site with a code and state arguments:

https://example.com/github?code=42..e9&state=d..t

Use .fetch_token to obtain access token. This method will also verify the state in case of CSRF attack:

>>> authorization_response = 'https://example.com/github?code=42..e9&state=d..t'
>>> token_endpoint = 'https://github.com/login/oauth/access_token'
>>> token = client.fetch_token(token_endpoint, authorization_response=authorization_response)
>>> print(token)
{
    'access_token': 'e..ad',
    'token_type': 'bearer',
    'scope': 'user:email'
}

Save this token to access users’ protected resources.

In real project, this session can not be re-used since you are redirected to another website. You need to create another session yourself:

>>> state = restore_previous_state()
>>>
>>> # using requests
>>> from authlib.integrations.requests_client import OAuth2Session
>>> client = OAuth2Session(client_id, client_secret, state=state)
>>>
>>> # using httpx
>>> from authlib.integrations.httpx_client import AsyncOAuth2Client
>>> client = AsyncOAuth2Client(client_id, client_secret, state=state)
>>>
>>> await client.fetch_token(token_endpoint, authorization_response=authorization_response)

Authlib has a built-in Flask/Django integration. Learn from them.

Add PKCE for Authorization Code

Authlib client can handle PKCE automatically, just pass code_verifier to create_authorization_url and fetch_token:

>>> client = OAuth2Session(..., code_challenge_method='S256')
>>> code_verifier = generate_token(48)
>>> uri, state = client.create_authorization_url(authorization_endpoint, code_verifier=code_verifier)
>>> # ...
>>> token = client.fetch_token(..., code_verifier=code_verifier)
OAuth2Session for Implicit

OAuth2Session supports implicit grant type. It can fetch the access token with the response_type of token:

>>> uri, state = client.create_authorization_url(authorization_endpoint, response_type='token')
>>> print(uri)
https://some-service.com/oauth/authorize?response_type=token&client_id=be..4d&...

Visit this link, and grant the authorization, the OAuth authorization server will redirect back to your redirect_uri, the response url would be something like:

https://example.com/cb#access_token=2..WpA&state=xyz&token_type=bearer&expires_in=3600

Fetch access token from the fragment with .fetch_token method:

>>> token = client.fetch_token(authorization_response=authorization_response)
>>> # if you don't specify access token endpoint, it will fetch from fragment.
>>> print(token)
{'access_token': '2..WpA', 'token_type': 'bearer', 'expires_in': 3600}

Note

GitHub doesn’t support token response type, try with other services.

OAuth2Session for Password

The password grant type is supported since Version 0.5. Use username and password to fetch the access token:

>>> token = client.fetch_token(token_endpoint, username='a-name', password='a-password')
OAuth2Session for Client Credentials

The client_credentials grant type is supported since Version 0.5. If no code or no user info provided, it would be a client_credentials request. But it is suggested that you specify a grant_type for it:

>>> token = client.fetch_token(token_endpoint)
>>> # or with grant_type
>>> token = client.fetch_token(token_endpoint, grant_type='client_credentials')
Client Authentication

When fetching access token, the authorization server will require a client authentication, Authlib provides three default methods defined by RFC7591:

  • client_secret_basic

  • client_secret_post

  • none

The default value is client_secret_basic. You can change the auth method with token_endpoint_auth_method:

>>> client = OAuth2Session(token_endpoint_auth_method='client_secret_post')

If the authorization server requires other means of authentication, you can construct an auth for your own need, and pass it to fetch_token:

>>> auth = YourAuth(...)
>>> token = client.fetch_token(token_endpoint, auth=auth, ...)

It is also possible to extend the client authentication method with .register_client_auth_method. Besides the default three authentication methods, there are more provided by Authlib. e.g.

  • client_secret_jwt

  • private_key_jwt

These two methods are defined by RFC7523 and OpenID Connect. Find more in Using JWTs Client Assertion in OAuth2Session.

There are still cases that developers need to define a custom client authentication method. Take issue#158 as an example, the provider requires us put client_id and client_secret on URL when sending POST request:

POST /oauth/token?grant_type=code&code=...&client_id=...&client_secret=...

Let’s call this weird authentication method client_secret_uri, and this is how we can get our OAuth 2.0 client authenticated:

from authlib.common.urls import add_params_to_uri

def auth_client_secret_uri(client, method, uri, headers, body):
    uri = add_params_to_uri(uri, [
        ('client_id', client.client_id),
        ('client_secret', client.client_secret),
    ])
    uri = uri + '&' + body
    body = ''
    return uri, headers, body

client = OAuth2Session(
    'client_id', 'client_secret',
    token_endpoint_auth_method='client_secret_uri',
    ...
)
client.register_client_auth_method(('client_secret_uri', auth_client_secret_uri))

With client_secret_uri registered, OAuth 2.0 client will authenticate with the signed URI. It is also possible to assign the function to token_endpoint_auth_method directly:

client = OAuth2Session(
    'client_id', 'client_secret',
    token_endpoint_auth_method=auth_client_secret_uri,
)
Access Protected Resources

Now you can access the protected resources. If you re-use the session, you don’t need to do anything:

>>> account_url = 'https://api.github.com/user'
>>> resp = client.get(account_url)
<Response [200]>
>>> resp.json()
{...}

The above is not the real flow, just like what we did in Fetch Token, we need to create another session ourselves:

>>> token = restore_previous_token_from_database()
>>> # token is a dict which must contain ``access_token``, ``token_type``
>>> client = OAuth2Session(client_id, client_secret, token=token)
>>> account_url = 'https://api.github.com/user'
>>> resp = client.get(account_url)
Refresh & Auto Update Token

It is possible that your previously saved token is expired when accessing protected resources. In this case, we can refresh the token manually, or even better, Authlib will refresh the token automatically and update the token for us.

Automatically refreshing tokens

If your OAuth2Session class was created with the token_endpoint parameter, Authlib will automatically refresh the token when it has expired:

>>> openid_configuration = requests.get("https://example.org/.well-known/openid-configuration").json()
>>> session = OAuth2Session(…, token_endpoint=openid_configuration["token_endpoint"])
Manually refreshing tokens

To call refresh_token() manually means we are going to exchange a new “access_token” with “refresh_token”:

>>> token = restore_previous_token_from_database()
>>> new_token = client.refresh_token(token_endpoint, refresh_token=token.refresh_token)

Authlib can also refresh a new token automatically when requesting resources. This is done by passing a update_token function when constructing the client instance:

def update_token(token, refresh_token=None, access_token=None):
    if refresh_token:
        item = OAuth2Token.find(name=name, refresh_token=refresh_token)
    elif access_token:
        item = OAuth2Token.find(name=name, access_token=access_token)
    else:
        return

    # update old token
    item.access_token = token['access_token']
    item.refresh_token = token.get('refresh_token')
    item.expires_at = token['expires_at']
    item.save()

client = OAuth2Session(client_id, client_secret, update_token=update_token)

When sending a request to resources endpoint, if our previously saved token is expired, this client will invoke .refresh_token method itself and call this our defined update_token to save the new token:

token = restore_previous_token_from_database()
client.token = token

# if the token is expired, this GET request will update token
client.get('https://openidconnect.googleapis.com/v1/userinfo')
Revoke and Introspect Token

If the provider support token revocation and introspection, you can revoke and introspect the token with:

token_endpoint = 'https://example.com/oauth/token'

token = get_your_previous_saved_token()
client.revoke_token(token_endpoint, token=token)
client.introspect_token(token_endpoint, token=token)

You can find the available parameters in API docs:

Compliance Fix for non Standard

There are services that claimed they are providing OAuth API, but with a little differences. Some services even return with the wrong Content Type. Compliance hooks are provided to solve those problems:

  • access_token_response: invoked before token parsing.

  • refresh_token_response: invoked before refresh token parsing.

  • protected_request: invoked before making a request.

For instance, Stackoverflow MUST add a site parameter in query string to protect users’ resources. And stackoverflow’s response is not in JSON. Let’s fix it:

from authlib.common.urls import add_params_to_uri, url_decode

def _non_compliant_param_name(url, headers, data):
    params = {'site': 'stackoverflow'}
    url = add_params_to_uri(url, params)
    return url, headers, body

def _fix_token_response(resp):
    data = dict(url_decode(resp.text))
    data['token_type'] = 'Bearer'
    data['expires_in'] = int(data['expires'])
    resp.json = lambda: data
    return resp

session.register_compliance_hook(
    'protected_request', _non_compliant_param_name)
session.register_compliance_hook(
    'access_token_response', _fix_token_response)

If you find a non standard OAuth 2 services, and you can’t fix it. Please report it in GitHub issues.

OAuth 2 OpenID Connect

For services that support OpenID Connect, if a scope of openid is provided, the authorization server will return a value of id_token in response:

>>> client_id = 'Your Google client ID'
>>> client_secret = 'Your Google client secret'
>>> scope = 'openid email profile'
>>> # using requests
>>> client = OAuth2Session(client_id, client_secret, scope=scope)
>>> # using httpx
>>> client = AsyncOAuth2Client(client_id, client_secret, scope=scope)

The remote server may require other parameters for OpenID Connect requests, for instance, it may require a nonce parameter, in this case, you need to generate it yourself, and pass it to create_authorization_url:

>>> from authlib.common.security import generate_token
>>> # remember to save this nonce for verification
>>> nonce = generate_token()
>>> client.create_authorization_url(url, redirect_uri='xxx', nonce=nonce, ...)

At the last step of client.fetch_token, the return value contains a id_token:

>>> resp = session.fetch_token(...)
>>> print(resp['id_token'])

This id_token is a JWT text, it can not be used unless it is parsed. Authlib has provided tools for parsing and validating OpenID Connect id_token:

>>> from authlib.oidc.core import CodeIDToken
>>> from authlib.jose import jwt
>>> # GET keys from https://www.googleapis.com/oauth2/v3/certs
>>> claims = jwt.decode(resp['id_token'], keys, claims_cls=CodeIDToken)
>>> claims.validate()

Get deep inside with JsonWebToken and CodeIDToken. Learn how to validate JWT claims at JSON Web Token (JWT).

AssertionSession

AssertionSession is a Requests Session for Assertion Framework of OAuth 2.0 Authorization Grants. It is also know as service account. A configured AssertionSession with handle token authorization automatically, which means you can just use it.

Take Google Service Account as an example, with the information in your service account JSON configure file:

import json
from authlib.integrations.requests_client import AssertionSession

with open('MyProject-1234.json') as f:
    conf = json.load(f)

token_uri = conf['token_uri']
header = {'alg': 'RS256'}
key_id = conf.get('private_key_id')
if key_id:
    header['kid'] = key_id

# Google puts scope in payload
claims = {'scope': scope}

session = AssertionSession(
    token_endpoint=token_uri,
    issuer=conf['client_email'],
    audience=token_uri,
    claims=claims,
    subject=None,
    key=conf['private_key'],
    header=header,
)
session.get(...)
session.post(...)

There is a ready to use GoogleServiceAccount in loginpass. You can also read these posts:

OAuth for Requests

Requests is a very popular HTTP library for Python. Authlib enables OAuth 1.0 and OAuth 2.0 for Requests with its OAuth1Session, OAuth2Session and AssertionSession.

Requests OAuth 1.0

There are three steps in OAuth 1 Session to obtain an access token:

  1. fetch a temporary credential

  2. visit the authorization page

  3. exchange access token with the temporary credential

It shares a common API design with OAuth for HTTPX.

OAuth1Session

The requests integration follows our common guide of OAuth 1 Session. Follow the documentation in OAuth 1 Session instead.

OAuth1Auth

It is also possible to use OAuth1Auth directly with in requests. After we obtained access token from an OAuth 1.0 provider, we can construct an auth instance for requests:

auth = OAuth1Auth(
    client_id='YOUR-CLIENT-ID',
    client_secret='YOUR-CLIENT-SECRET',
    token='oauth_token',
    token_secret='oauth_token_secret',
)
requests.get(url, auth=auth)
Requests OAuth 2.0

In OAuth 2 Session, there are many grant types, including:

  1. Authorization Code Flow

  2. Implicit Flow

  3. Password Flow

  4. Client Credentials Flow

And also, Authlib supports non Standard OAuth 2.0 providers via Compliance Fix.

Follow the common guide of OAuth 2 Session to find out how to use requests integration of OAuth 2.0 flow.

Using client_secret_jwt in Requests

There are three default client authentication methods defined for OAuth2Session. But what if you want to use client_secret_jwt instead? client_secret_jwt is defined in RFC7523, use it for Requests:

from authlib.integrations.requests_client import OAuth2Session
from authlib.oauth2.rfc7523 import ClientSecretJWT

token_endpoint = 'https://example.com/oauth/token'
session = OAuth2Session(
    'your-client-id', 'your-client-secret',
    token_endpoint_auth_method=ClientSecretJWT(token_endpoint),
)
session.fetch_token(token_endpoint)

The ClientSecretJWT is provided by RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.

Using private_key_jwt in Requests

What if you want to use private_key_jwt client authentication method, here is the way with PrivateKeyJWT for Requests:

from authlib.integrations.requests_client import OAuth2Session
from authlib.oauth2.rfc7523 import PrivateKeyJWT

with open('your-private-key.pem', 'rb') as f:
    private_key = f.read()

token_endpoint = 'https://example.com/oauth/token'
session = OAuth2Session(
    'your-client-id', private_key,
    token_endpoint_auth_method=PrivateKeyJWT(token_endpoint),
)
session.fetch_token(token_endpoint)

The PrivateKeyJWT is provided by RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.

OAuth2Auth

Already obtained access token? We can use OAuth2Auth directly in requests. But this OAuth2Auth can not refresh token automatically for you. Here is how to use it in requests:

token = {'token_type': 'bearer', 'access_token': '....', ...}
auth = OAuth2Auth(token)
requests.get(url, auth=auth)
Requests OpenID Connect

OpenID Connect is built on OAuth 2.0. It is pretty simple to communicate with an OpenID Connect provider via Authlib. With Authlib built-in OAuth 2.0 system and JsonWebToken (JWT), parsing OpenID Connect id_token could be very easy.

Understand how it works with OAuth 2 OpenID Connect.

Requests Service Account

The Assertion Framework of OAuth 2.0 Authorization Grants is also known as service account. With the implementation of AssertionSession, we can easily integrate with a “assertion” service.

Checking out an example of Google Service Account with AssertionSession.

Close Session Hint

Developers SHOULD close a Requests Session when the jobs are done. You can call .close() manually, or use a with context to automatically close the session:

session = OAuth2Session(client_id, client_secret)
session.get(url)
session.close()

with OAuth2Session(client_id, client_secret) as session:
    session.get(url)
Self-Signed Certificate

Self-signed certificate mutual-TLS method internet standard is defined in RFC8705 Section 2.2 .

For specifics development purposes only, you may need to disable SSL verification.

You can force all requests to disable SSL verification by setting your environment variable CURL_CA_BUNDLE="".

This solutions works because Python requests (and most of the packages) overwrites the default value for ssl verifications from environment variables CURL_CA_BUNDLE and REQUESTS_CA_BUNDLE.

This hack will only work with CURL_CA_BUNDLE, as you can see in requests/sessions.py

verify = (os.environ.get('REQUESTS_CA_BUNDLE')
or os.environ.get('CURL_CA_BUNDLE'))

Please remember to set the env variable only in you development environment.

OAuth for HTTPX

HTTPX is a next-generation HTTP client for Python. Authlib enables OAuth 1.0 and OAuth 2.0 for HTTPX with its async versions:

Note

HTTPX is still in its “alpha” stage, use it with caution.

HTTPX OAuth 1.0

There are three steps in OAuth 1 to obtain an access token:

  1. fetch a temporary credential

  2. visit the authorization page

  3. exchange access token with the temporary credential

It shares a common API design with OAuth for Requests.

Read the common guide of OAuth 1 Session to understand the whole OAuth 1.0 flow.

HTTPX OAuth 2.0

In OAuth 2 Session, there are many grant types, including:

  1. Authorization Code Flow

  2. Implicit Flow

  3. Password Flow

  4. Client Credentials Flow

And also, Authlib supports non Standard OAuth 2.0 providers via Compliance Fix.

Read the common guide of OAuth 2 Session to understand the whole OAuth 2.0 flow.

Using client_secret_jwt in HTTPX

Here is how you could register and use client_secret_jwt client authentication method for HTTPX:

from authlib.integrations.httpx_client import AsyncOAuth2Client
from authlib.oauth2.rfc7523 import ClientSecretJWT

client = AsyncOAuth2Client(
    'your-client-id', 'your-client-secret',
    token_endpoint_auth_method='client_secret_jwt'
)
token_endpoint = 'https://example.com/oauth/token'
client.register_client_auth_method(ClientSecretJWT(token_endpoint))
client.fetch_token(token_endpoint)

The ClientSecretJWT is provided by RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.

Using private_key_jwt in HTTPX

Here is how you could register and use private_key_jwt client authentication method for HTTPX:

from authlib.integrations.httpx_client import AsyncOAuth2Client
from authlib.oauth2.rfc7523 import PrivateKeyJWT

with open('your-private-key.pem', 'rb') as f:
    private_key = f.read()

client = AsyncOAuth2Client(
    'your-client-id', private_key,
    token_endpoint_auth_method='private_key_jwt',
)
token_endpoint = 'https://example.com/oauth/token'
client.register_client_auth_method(PrivateKeyJWT(token_endpoint))
client.fetch_token(token_endpoint)

The PrivateKeyJWT is provided by RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.

Async OAuth 1.0

The async version of AsyncOAuth1Client works the same as OAuth 1 Session, except that we need to add await when required:

# fetching request token
request_token = await client.fetch_request_token(request_token_url)

# fetching access token
access_token = await client.fetch_access_token(access_token_url)

# normal requests
await client.get(...)
await client.post(...)
await client.put(...)
await client.delete(...)
Async OAuth 2.0

The async version of AsyncOAuth2Client works the same as OAuth 2 Session, except that we need to add await when required:

# fetching access token
token = await client.fetch_token(token_endpoint, ...)

# normal requests
await client.get(...)
await client.post(...)
await client.put(...)
await client.delete(...)
Auto Update Token

The AsyncOAuth2Client also supports update_token parameter, the update_token can either be sync and async. For instance:

async def update_token(token, refresh_token=None, access_token=None):
    if refresh_token:
        item = await OAuth2Token.find(name=name, refresh_token=refresh_token)
    elif access_token:
        item = await OAuth2Token.find(name=name, access_token=access_token)
    else:
        return

    # update old token
    item.access_token = token['access_token']
    item.refresh_token = token.get('refresh_token')
    item.expires_at = token['expires_at']
    await item.save()

Then pass this update_token into AsyncOAuth2Client.

Async Service Account

AsyncAssertionClient is the async version for Assertion Framework of OAuth 2.0 Authorization Grants. It is also know as service account. A configured AsyncAssertionClient will handle token authorization automatically, which means you can just use it.

Take Google Service Account as an example, with the information in your service account JSON configure file:

import json
from authlib.integrations.httpx_client import AsyncAssertionClient

with open('MyProject-1234.json') as f:
    conf = json.load(f)

token_uri = conf['token_uri']
header = {'alg': 'RS256'}
key_id = conf.get('private_key_id')
if key_id:
    header['kid'] = key_id

# Google puts scope in payload
claims = {'scope': scope}

async def main():
    client = AsyncAssertionClient(
        token_endpoint=token_uri,
        issuer=conf['client_email'],
        audience=token_uri,
        claims=claims,
        subject=None,
        key=conf['private_key'],
        header=header,
    )
    resp = await client.get(...)
    resp = await client.post(...)
Close Client Hint

Developers SHOULD close a HTTPX Session when the jobs are done. You can call .close() manually, or use a with context to automatically close the session:

client = OAuth2Client(client_id, client_secret)
client.get(url)
client.close()

with OAuth2Client(client_id, client_secret) as client:
    client.get(url)

For async OAuth Client, use await client.close():

client = AsyncOAuth2Client(client_id, client_secret)
await client.get(url)
await client.close()

async with AsyncOAuth2Client(client_id, client_secret) as client:
    await client.get(url)

Our Web OAuth Clients will close every session automatically, no need to worry.

Web OAuth Clients

This documentation covers OAuth 1.0 and OAuth 2.0 integrations for Python Web Frameworks like:

  • Django: The web framework for perfectionists with deadlines

  • Flask: The Python micro framework for building web applications

  • Starlette: The little ASGI framework that shines

Authlib shares a common API design among these web frameworks. Instead of introducing them one by one, this documentation contains the common usage for them all.

We start with creating a registry with the OAuth class:

# for Flask framework
from authlib.integrations.flask_client import OAuth

# for Django framework
from authlib.integrations.django_client import OAuth

# for Starlette framework
from authlib.integrations.starlette_client import OAuth

oauth = OAuth()

There are little differences among each framework, you can read their documentation later:

  1. flask_client.OAuth for Flask OAuth Client

  2. django_client.OAuth for Django OAuth Client

  3. starlette_client.OAuth for Starlette OAuth Client

The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.

Log In with OAuth 1.0

For instance, Twitter is an OAuth 1.0 service, you want your users to log in your website with Twitter.

The first step is register a remote application on the OAuth registry via oauth.register method:

oauth.register(
    name='twitter',
    client_id='{{ your-twitter-consumer-key }}',
    client_secret='{{ your-twitter-consumer-secret }}',
    request_token_url='https://api.twitter.com/oauth/request_token',
    request_token_params=None,
    access_token_url='https://api.twitter.com/oauth/access_token',
    access_token_params=None,
    authorize_url='https://api.twitter.com/oauth/authenticate',
    authorize_params=None,
    api_base_url='https://api.twitter.com/1.1/',
    client_kwargs=None,
)

The first parameter in register method is the name of the remote application. You can access the remote application with:

twitter = oauth.create_client('twitter')
# or simply with
twitter = oauth.twitter

The configuration of those parameters can be loaded from the framework configuration. Each framework has its own config system, read the framework specified documentation later.

For instance, if client_id and client_secret can be loaded via configuration, we can simply register the remote app with:

oauth.register(
    name='twitter',
    request_token_url='https://api.twitter.com/oauth/request_token',
    access_token_url='https://api.twitter.com/oauth/access_token',
    authorize_url='https://api.twitter.com/oauth/authenticate',
    api_base_url='https://api.twitter.com/1.1/',
)

The client_kwargs is a dict configuration to pass extra parameters to OAuth 1 Session. If you are using RSA-SHA1 signature method:

client_kwargs = {
    'signature_method': 'RSA-SHA1',
    'signature_type': 'HEADER',
    'rsa_key': 'Your-RSA-Key'
}
Saving Temporary Credential

Usually, the framework integration has already implemented this part through the framework session system. All you need to do is enable session for the chosen framework.

Routes for Authorization

After configuring the OAuth registry and the remote application, the rest steps are much simpler. The only required parts are routes:

  1. redirect to 3rd party provider (Twitter) for authentication

  2. redirect back to your website to fetch access token and profile

Here is the example for Twitter login:

def login(request):
    twitter = oauth.create_client('twitter')
    redirect_uri = 'https://example.com/authorize'
    return twitter.authorize_redirect(request, redirect_uri)

def authorize(request):
    twitter = oauth.create_client('twitter')
    token = twitter.authorize_access_token(request)
    resp = twitter.get('account/verify_credentials.json')
    resp.raise_for_status()
    profile = resp.json()
    # do something with the token and profile
    return '...'

After user confirmed on Twitter authorization page, it will redirect back to your website authorize page. In this route, you can get your user’s twitter profile information, you can store the user information in your database, mark your user as logged in and etc.

Using OAuth 2.0 to Log In

For instance, GitHub is an OAuth 2.0 service, you want your users to log in your website with GitHub.

The first step is register a remote application on the OAuth registry via oauth.register method:

oauth.register(
    name='github',
    client_id='{{ your-github-client-id }}',
    client_secret='{{ your-github-client-secret }}',
    access_token_url='https://github.com/login/oauth/access_token',
    access_token_params=None,
    authorize_url='https://github.com/login/oauth/authorize',
    authorize_params=None,
    api_base_url='https://api.github.com/',
    client_kwargs={'scope': 'user:email'},
)

The first parameter in register method is the name of the remote application. You can access the remote application with:

github = oauth.create_client('github')
# or simply with
github = oauth.github

The configuration of those parameters can be loaded from the framework configuration. Each framework has its own config system, read the framework specified documentation later.

The client_kwargs is a dict configuration to pass extra parameters to OAuth 2 Session, you can pass extra parameters like:

client_kwargs = {
    'scope': 'profile',
    'token_endpoint_auth_method': 'client_secret_basic',
    'token_placement': 'header',
}

There are several token_endpoint_auth_method, get a deep inside the Client Authentication Methods.

Note

Authlib is using request_token_url to detect if the client is an OAuth 1.0 or OAuth 2.0 client. In OAuth 2.0, there is no request_token_url.

Routes for Authorization

After configuring the OAuth registry and the remote application, the rest steps are much simpler. The only required parts are routes:

  1. redirect to 3rd party provider (GitHub) for authentication

  2. redirect back to your website to fetch access token and profile

Here is the example for GitHub login:

def login(request):
    github = oauth.create_client('github')
    redirect_uri = 'https://example.com/authorize'
    return github.authorize_redirect(request, redirect_uri)

def authorize(request):
    token = oauth.github.authorize_access_token(request)
    resp = oauth.github.get('user', token=token)
    resp.raise_for_status()
    profile = resp.json()
    # do something with the token and profile
    return '...'

After user confirmed on GitHub authorization page, it will redirect back to your website authorize. In this route, you can get your user’s GitHub profile information, you can store the user information in your database, mark your user as logged in and etc.

Note

You may find that our documentation for OAuth 1.0 and OAuth 2.0 are the same. They are designed to share the same API, so that you use the same code for both OAuth 1.0 and OAuth 2.0.

The ONLY difference is the configuration. OAuth 1.0 contains request_token_url and request_token_params while OAuth 2.0 not. Also, the client_kwargs are different.

Client Authentication Methods

When fetching access token, the authorization server will require a client authentication, Authlib provides three default methods defined by RFC7591:

  • client_secret_basic

  • client_secret_post

  • none

But if the remote provider does not support these three methods, we need to register our own authentication methods, like Client Authentication:

from authlib.oauth2.rfc7523 import ClientSecretJWT

oauth.register(
    'name',
    ...
    client_auth_methods=[
        ClientSecretJWT(token_endpoint),  # client_secret_jwt
    ]
)

New in version v0.15: Starting from v0.15, developers can add custom authentication methods directly to token endpoint:

oauth.register(
    'name',
    ...
    token_endpoint_auth_method=ClientSecretJWT(token_endpoint),
)
Accessing OAuth Resources

Note

If your application ONLY needs login via 3rd party services like Twitter, Google, Facebook and GitHub to login, you DON’T need to create the token database.

There are also chances that you need to access your user’s 3rd party OAuth provider resources. For instance, you want to display the logged in user’s twitter time line and GitHub repositories. You will use access token to fetch the resources:

def get_twitter_tweets(request):
    token = OAuth1Token.find(
        name='twitter',
        user=request.user
    )
    # API URL: https://api.twitter.com/1.1/statuses/user_timeline.json
    resp = oauth.twitter.get('statuses/user_timeline.json', token=token.to_token())
    resp.raise_for_status()
    return resp.json()

def get_github_repositories(request):
    token = OAuth2Token.find(
        name='github',
        user=request.user
    )
    # API URL: https://api.github.com/user/repos
    resp = oauth.github.get('user/repos', token=token.to_token())
    resp.raise_for_status()
    return resp.json()

In this case, we need a place to store the access token in order to use it later. Usually we will save the token into database. In the previous Routes for Authorization authorize part, we can save the token into database.

Design Database

It is possible to share one database table for both OAuth 1.0 token and OAuth 2.0 token. It is also good to use different database tables for OAuth 1.0 and OAuth 2.0.

In the above example, we are using two tables. Here are some hints on how to design the database:

class OAuth1Token(Model):
    name = String(length=40)
    oauth_token = String(length=200)
    oauth_token_secret = String(length=200)
    user = ForeignKey(User)

    def to_token(self):
        return dict(
            oauth_token=self.access_token,
            oauth_token_secret=self.alt_token,
        )

class OAuth2Token(Model):
    name = String(length=40)
    token_type = String(length=40)
    access_token = String(length=200)
    refresh_token = String(length=200)
    expires_at = PositiveIntegerField()
    user = ForeignKey(User)

    def to_token(self):
        return dict(
            access_token=self.access_token,
            token_type=self.token_type,
            refresh_token=self.refresh_token,
            expires_at=self.expires_at,
        )

And then we can save user’s access token into database when user was redirected back to our authorize page.

Fetch User OAuth Token

You can always pass a token parameter to the remote application request methods, like:

token = OAuth1Token.find(name='twitter', user=request.user)
oauth.twitter.get(url, token=token)
oauth.twitter.post(url, token=token)
oauth.twitter.put(url, token=token)
oauth.twitter.delete(url, token=token)

token = OAuth2Token.find(name='github', user=request.user)
oauth.github.get(url, token=token)
oauth.github.post(url, token=token)
oauth.github.put(url, token=token)
oauth.github.delete(url, token=token)

However, it is not a good practice to query the token database in every request function. Authlib provides a way to fetch current user’s token automatically for you, just register with fetch_token function:

def fetch_twitter_token(request):
    token = OAuth1Token.find(
        name='twitter',
        user=request.user
    )
    return token.to_token()

def fetch_github_token(request):
    token = OAuth2Token.find(
        name='github',
        user=request.user
    )
    return token.to_token()

# we can registry this ``fetch_token`` with oauth.register
oauth.register(
    'twitter',
    # ...
    fetch_token=fetch_twitter_token,
)
oauth.register(
    'github',
    # ...
    fetch_token=fetch_github_token,
)

Not good enough. In this way, you have to write fetch_token for every remote application. There is also a shared way to fetch token:

def fetch_token(name, request):
    if name in OAUTH1_SERVICES:
        model = OAuth1Token
    else:
        model = OAuth2Token

    token = model.find(
        name=name,
        user=request.user
    )
    return token.to_token()

# initialize OAuth registry with this fetch_token function
oauth = OAuth(fetch_token=fetch_token)

Now, developers don’t have to pass a token in the HTTP requests, instead, they can pass the request:

def get_twitter_tweets(request):
    resp = oauth.twitter.get('statuses/user_timeline.json', request=request)
    resp.raise_for_status()
    return resp.json()

Note

Flask is different, you don’t need to pass the request either.

OAuth 2.0 Enhancement

OAuth 1.0 is a protocol, while OAuth 2.0 is a framework. There are so many features in OAuth 2.0 than OAuth 1.0. This section is designed for OAuth 2.0 specially.

Auto Update Token

In OAuth 1.0, access token never expires. But in OAuth 2.0, token MAY expire. If there is a refresh_token value, Authlib will auto update the access token if it is expired.

We do this by passing a update_token function to OAuth registry:

def update_token(name, token, refresh_token=None, access_token=None):
    if refresh_token:
        item = OAuth2Token.find(name=name, refresh_token=refresh_token)
    elif access_token:
        item = OAuth2Token.find(name=name, access_token=access_token)
    else:
        return

    # update old token
    item.access_token = token['access_token']
    item.refresh_token = token.get('refresh_token')
    item.expires_at = token['expires_at']
    item.save()

oauth = OAuth(update_token=update_token)

In this way, OAuth 2.0 integration will update expired token automatically. There is also a signal way to update token. Checkout the frameworks documentation.

OAuth 2.0 Code Challenge

Adding code_challenge provided by RFC7636: Proof Key for Code Exchange by OAuth Public Clients is simple. You register your remote app with a code_challenge_method in client_kwargs:

oauth.register(
    'example',
    client_id='Example Client ID',
    client_secret='Example Client Secret',
    access_token_url='https://example.com/oauth/access_token',
    authorize_url='https://example.com/oauth/authorize',
    api_base_url='https://api.example.com/',
    client_kwargs={'code_challenge_method': 'S256'},
)

Note, the only supported code_challenge_method is S256.

Compliance Fix for OAuth 2.0

For non standard OAuth 2.0 service, you can pass a compliance_fix when .register. For example, Slack has a compliance problem, we can construct a method to fix the requests session:

def slack_compliance_fix(session):
    def _fix(resp):
        resp.raise_for_status()
        token = resp.json()
        # slack returns no token_type
        token['token_type'] = 'Bearer'
        resp._content = to_unicode(json.dumps(token)).encode('utf-8')
        return resp
    session.register_compliance_hook('access_token_response', _fix)

Then pass this slack_compliance_fix into .register parameters:

oauth.register(
    'slack',
    client_id='...',
    client_secret='...',
    ...,
    compliance_fix=slack_compliance_fix,
    ...
)

Find all the available compliance hooks at Compliance Fix for non Standard.

OpenID Connect & UserInfo

When logging in with OpenID Connect, “access_token” is not what developers want. Instead, what developers want is user info, Authlib wrap it with UserInfo.

There are two ways to fetch userinfo from 3rd party providers. If the provider supports OpenID Connect, we can get the user info from the returned id_token.

userinfo_endpoint

Passing a userinfo_endpoint when .register remote client:

oauth.register(
    'google',
    client_id='...',
    client_secret='...',
    userinfo_endpoint='https://openidconnect.googleapis.com/v1/userinfo',
)

And later, when the client has obtained the access token, we can call:

def authorize(request):
    token = oauth.google.authorize_access_token(request)
    user = oauth.google.userinfo(request)
    return '...'
Parsing id_token

For OpenID Connect provider, when .authorize_access_token, the provider will include a id_token in the response. This id_token contains the UserInfo we need so that we don’t have to fetch userinfo endpoint again.

The id_token is a JWT, with Authlib JSON Web Token (JWT), we can decode it easily. Frameworks integrations will handle it automatically if configurations are correct.

A simple solution is to provide the OpenID Connect Discovery Endpoint:

oauth.register(
    'google',
    client_id='...',
    client_secret='...',
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid email profile'},
)

The discovery endpoint provides all the information we need so that we don’t have to add authorize_url and access_token_url.

Check out our client example: https://github.com/authlib/demo-oauth-client

But if there is no discovery endpoint, developers MUST add all the missing information themselves:

* authorize_url
* access_token_url
* jwks_uri

This jwks_uri is the URL to get provider’s public JWKs. Developers MAY also provide the value of jwks instead of jwks_uri:

oauth.register(
    'google',
    client_id='...',
    client_secret='...',
    access_token_url='https://example.com/oauth/access_token',
    authorize_url='https://example.com/oauth/authorize',
    jwks={"keys": [...]}
)

Flask OAuth Client

This documentation covers OAuth 1.0, OAuth 2.0 and OpenID Connect Client support for Flask. Looking for OAuth providers?

Flask OAuth client can handle OAuth 1 and OAuth 2 services. It shares a similar API with Flask-OAuthlib, you can transfer your code from Flask-OAuthlib to Authlib with ease.

Create a registry with OAuth object:

from authlib.integrations.flask_client import OAuth

oauth = OAuth(app)

You can also initialize it later with init_app() method:

oauth = OAuth()
oauth.init_app(app)

The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.

Important

Please read Web OAuth Clients at first. Authlib has a shared API design among framework integrations, learn them from Web OAuth Clients.

Configuration

Authlib Flask OAuth registry can load the configuration from Flask app.config automatically. Every key value pair in .register can be omitted. They can be configured in your Flask App configuration. Config keys are formatted as {name}_{key} in uppercase, e.g.

TWITTER_CLIENT_ID

Twitter Consumer Key

TWITTER_CLIENT_SECRET

Twitter Consumer Secret

TWITTER_REQUEST_TOKEN_URL

URL to fetch OAuth request token

If you register your remote app as oauth.register('example', ...), the config keys would look like:

EXAMPLE_CLIENT_ID

OAuth Consumer Key

EXAMPLE_CLIENT_SECRET

OAuth Consumer Secret

EXAMPLE_ACCESS_TOKEN_URL

URL to fetch OAuth access token

Here is a full list of the configuration keys:

  • {name}_CLIENT_ID: Client key of OAuth 1, or Client ID of OAuth 2

  • {name}_CLIENT_SECRET: Client secret of OAuth 2, or Client Secret of OAuth 2

  • {name}_REQUEST_TOKEN_URL: Request Token endpoint for OAuth 1

  • {name}_REQUEST_TOKEN_PARAMS: Extra parameters for Request Token endpoint

  • {name}_ACCESS_TOKEN_URL: Access Token endpoint for OAuth 1 and OAuth 2

  • {name}_ACCESS_TOKEN_PARAMS: Extra parameters for Access Token endpoint

  • {name}_AUTHORIZE_URL: Endpoint for user authorization of OAuth 1 ro OAuth 2

  • {name}_AUTHORIZE_PARAMS: Extra parameters for Authorization Endpoint.

  • {name}_API_BASE_URL: A base URL endpoint to make requests simple

  • {name}_CLIENT_KWARGS: Extra keyword arguments for OAuth1Session or OAuth2Session

We suggest that you keep ONLY {name}_CLIENT_ID and {name}_CLIENT_SECRET in your Flask application configuration.

Using Cache for Temporary Credential

By default, Flask OAuth registry will use Flask session to store OAuth 1.0 temporary credential (request token). However in this way, there are chances your temporary credential will be exposed.

Our OAuth registry provides a simple way to store temporary credentials in a cache system. When initializing OAuth, you can pass an cache instance:

oauth = OAuth(app, cache=cache)

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

A cache instance MUST have methods:

  • .get(key)

  • .set(key, value, expires=None)

Routes for Authorization

Unlike the examples in Web OAuth Clients, Flask does not pass a request into routes. In this case, the routes for authorization should look like:

from flask import url_for, render_template

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

@app.route('/authorize')
def authorize():
    token = oauth.twitter.authorize_access_token()
    resp = oauth.twitter.get('account/verify_credentials.json')
    resp.raise_for_status()
    profile = resp.json()
    # do something with the token and profile
    return redirect('/')
Accessing OAuth Resources

There is no request in accessing OAuth resources either. Just like above, we don’t need to pass request parameter, everything is handled by Authlib automatically:

from flask import render_template

@app.route('/github')
def show_github_profile():
    resp = oauth.github.get('user')
    resp.raise_for_status()
    profile = resp.json()
    return render_template('github.html', profile=profile)

In this case, our fetch_token could look like:

from your_project import current_user

def fetch_token(name):
    if name in OAUTH1_SERVICES:
        model = OAuth1Token
    else:
        model = OAuth2Token

    token = model.find(
        name=name,
        user=current_user,
    )
    return token.to_token()

# initialize OAuth registry with this fetch_token function
oauth = OAuth(fetch_token=fetch_token)

You don’t have to pass token, you don’t have to pass request. That is the fantasy of Flask.

Auto Update Token via Signal

New in version v0.13: The signal is added since v0.13

Instead of define a update_token method and passing it into OAuth registry, it is also possible to use signal to listen for token updating.

Before using signal, make sure you have installed blinker library:

$ pip install blinker

Connect the token_update signal:

from authlib.integrations.flask_client import token_update

@token_update.connect_via(app)
def on_token_update(sender, name, token, refresh_token=None, access_token=None):
    if refresh_token:
        item = OAuth2Token.find(name=name, refresh_token=refresh_token)
    elif access_token:
        item = OAuth2Token.find(name=name, access_token=access_token)
    else:
        return

    # update old token
    item.access_token = token['access_token']
    item.refresh_token = token.get('refresh_token')
    item.expires_at = token['expires_at']
    item.save()
Flask OpenID Connect Client

An OpenID Connect client is no different than a normal OAuth 2.0 client. When register with openid scope, the built-in Flask OAuth client will handle everything automatically:

oauth.register(
    'google',
    ...
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid profile email'}
)

When we get the returned token:

token = oauth.google.authorize_access_token()

There should be a id_token in the response. Authlib has called .parse_id_token automatically, we can get userinfo in the token:

userinfo = token['userinfo']
Examples

Here are some example projects for you to learn Flask OAuth client integrations:

  1. OAuth 1.0: Flask Twitter Login.

  2. OAuth 2.0 & OpenID Connect: Flask Google Login.

Django OAuth Client

Looking for OAuth providers?

The Django client can handle OAuth 1 and OAuth 2 services. Authlib has a shared API design among framework integrations. Get started with Web OAuth Clients.

Create a registry with OAuth object:

from authlib.integrations.django_client import OAuth

oauth = OAuth()

The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.

Important

Please read Web OAuth Clients at first. Authlib has a shared API design among framework integrations, learn them from Web OAuth Clients.

Configuration

Authlib Django OAuth registry can load the configuration from your Django application settings automatically. Every key value pair can be omitted. They can be configured from your Django settings:

AUTHLIB_OAUTH_CLIENTS = {
    'twitter': {
        'client_id': 'Twitter Consumer Key',
        'client_secret': 'Twitter Consumer Secret',
        'request_token_url': 'https://api.twitter.com/oauth/request_token',
        'request_token_params': None,
        'access_token_url': 'https://api.twitter.com/oauth/access_token',
        'access_token_params': None,
        'refresh_token_url': None,
        'authorize_url': 'https://api.twitter.com/oauth/authenticate',
        'api_base_url': 'https://api.twitter.com/1.1/',
        'client_kwargs': None
    }
}

There are differences between OAuth 1.0 and OAuth 2.0, please check the parameters in .register in Web OAuth Clients.

Saving Temporary Credential

In OAuth 1.0, we need to use a temporary credential to exchange access token, this temporary credential was created before redirecting to the provider (Twitter), we need to save this temporary credential somewhere in order to use it later.

In OAuth 1, Django client will save the request token in sessions. In this case, you just need to configure Session Middleware in Django:

MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware'
]

Follow the official Django documentation to set a proper session. Either a database backend or a cache backend would work well.

Warning

Be aware, using secure cookie as session backend will expose your request token.

Routes for Authorization

Just like the example in Web OAuth Clients, everything is the same. But there is a hint to create redirect_uri with request in Django:

def login(request):
    # build a full authorize callback uri
    redirect_uri = request.build_absolute_uri('/authorize')
    return oauth.twitter.authorize_redirect(request, redirect_uri)
Auto Update Token via Signal

Instead of defining an update_token method and passing it into OAuth registry, it is also possible to use signals to listen for token updates:

from django.dispatch import receiver
from authlib.integrations.django_client import token_update

@receiver(token_update)
def on_token_update(sender, name, token, refresh_token=None, access_token=None, **kwargs):
    if refresh_token:
        item = OAuth2Token.find(name=name, refresh_token=refresh_token)
    elif access_token:
        item = OAuth2Token.find(name=name, access_token=access_token)
    else:
        return

    # update old token
    item.access_token = token['access_token']
    item.refresh_token = token.get('refresh_token')
    item.expires_at = token['expires_at']
    item.save()
Django OpenID Connect Client

An OpenID Connect client is no different than a normal OAuth 2.0 client. When registered with the openid scope, the built-in Django OAuth client will handle everything automatically:

oauth.register(
    'google',
    ...
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid profile email'}
)

When we get the returned token:

token = oauth.google.authorize_access_token(request)

There should be a id_token in the response. Authlib has called .parse_id_token automatically, we can get userinfo in the token:

userinfo = token['userinfo']

Find Django Google login example at https://github.com/authlib/demo-oauth-client/tree/master/django-google-login

Starlette OAuth Client

Starlette is a lightweight ASGI framework/toolkit, which is ideal for building high performance asyncio services.

This documentation covers OAuth 1.0, OAuth 2.0 and OpenID Connect Client support for Starlette. Because all the frameworks integrations share the same API, it is best to:

Read Web OAuth Clients at first.

The difference between Starlette and Flask/Django integrations is Starlette is async. We will use await for the functions we need to call. But first, let’s create an OAuth instance:

from authlib.integrations.starlette_client import OAuth

oauth = OAuth()

The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.

Configuration

Starlette can load configuration from environment; Authlib implementation for Starlette client can use this configuration. Here is an example of how to do it:

from starlette.config import Config

config = Config('.env')
oauth = OAuth(config)

Authlib will load client_id and client_secret from the configuration, take google as an example:

oauth.register(name='google', ...)

It will load GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET from the environment.

Register Remote Apps

oauth.register is the same as Web OAuth Clients:

oauth.register(
    'google',
    client_id='...',
    client_secret='...',
    ...
)

However, unlike Flask/Django, Starlette OAuth registry is using HTTPX AsyncOAuth1Client and AsyncOAuth2Client as the OAuth backends. While Flask and Django are using the Requests version of OAuth1Session and OAuth2Session.

Enable Session for OAuth 1.0

With OAuth 1.0, we need to use a temporary credential to exchange for an access token. This temporary credential is created before redirecting to the provider (Twitter), and needs to be saved somewhere in order to use it later.

With OAuth 1, the Starlette client will save the request token in sessions. To enable this, we need to add the SessionMiddleware middleware to the application, which requires the installation of the itsdangerous package:

from starlette.applications import Starlette
from starlette.middleware.sessions import SessionMiddleware

app = Starlette()
app.add_middleware(SessionMiddleware, secret_key="some-random-string")

However, using the SessionMiddleware will store the temporary credential as a secure cookie which will expose your request token to the client.

Routes for Authorization

Just like the examples in Web OAuth Clients, but Starlette is async, the routes for authorization should look like:

@app.route('/login/google')
async def login_via_google(request):
    google = oauth.create_client('google')
    redirect_uri = request.url_for('authorize_google')
    return await google.authorize_redirect(request, redirect_uri)

@app.route('/auth/google')
async def authorize_google(request):
    google = oauth.create_client('google')
    token = await google.authorize_access_token(request)
    # do something with the token and userinfo
    return '...'
Starlette OpenID Connect

An OpenID Connect client is no different than a normal OAuth 2.0 client, just add openid scope when .register. The built-in Starlette OAuth client will handle everything automatically:

oauth.register(
    'google',
    ...
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid profile email'}
)

When we get the returned token:

token = await oauth.google.authorize_access_token()

There should be a id_token in the response. Authlib has called .parse_id_token automatically, we can get userinfo in the token:

userinfo = token['userinfo']
Examples

We have Starlette demos at https://github.com/authlib/demo-oauth-client

  1. OAuth 1.0: Starlette Twitter login

  2. OAuth 2.0: Starlette Google login

FastAPI OAuth Client

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. It is build on top of Starlette, that means most of the code looks similar with Starlette code. You should first read documentation of:

  1. Web OAuth Clients

  2. Starlette OAuth Client

Here is how you would create a FastAPI application:

from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware

app = FastAPI()
# we need this to save temporary code & state in session
app.add_middleware(SessionMiddleware, secret_key="some-random-string")

Since Authlib starlette requires using request instance, we need to expose that request to Authlib. According to the documentation on Using the Request Directly:

from starlette.requests import Request

@app.get("/login/google")
async def login_via_google(request: Request):
    redirect_uri = request.url_for('auth_via_google')
    return await oauth.google.authorize_redirect(request, redirect_uri)

@app.get("/auth/google")
async def auth_via_google(request: Request):
    token = await oauth.google.authorize_access_token(request)
    user = token['userinfo']
    return dict(user)

All other APIs are the same with Starlette.

FastAPI OAuth 1.0 Client

We have a blog post about how to create Twitter login in FastAPI:

https://blog.authlib.org/2020/fastapi-twitter-login

FastAPI OAuth 2.0 Client

We have a blog post about how to create Google login in FastAPI:

https://blog.authlib.org/2020/fastapi-google-login

Client API References

This part of the documentation covers the interface of Authlib Client.

Requests OAuth Sessions
class authlib.integrations.requests_client.OAuth1Session(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', force_include_body=False, **kwargs)
create_authorization_url(url, request_token=None, **kwargs)

Create an authorization URL by appending request_token and optional kwargs to url.

This is the second step in the OAuth 1 workflow. The user should be redirected to this authorization URL, grant access to you, and then be redirected back to you. The redirection back can either be specified during client registration or by supplying a callback URI per request.

Parameters
  • url – The authorization endpoint URL.

  • request_token – The previously obtained request token.

  • kwargs – Optional parameters to append to the URL.

Returns

The authorization URL with new parameters embedded.

fetch_access_token(url, verifier=None, **kwargs)

Method for fetching an access token from the token endpoint.

This is the final step in the OAuth 1 workflow. An access token is obtained using all previously obtained credentials, including the verifier from the authorization step.

Parameters
  • url – Access Token endpoint.

  • verifier – A verifier string to prove authorization was granted.

  • kwargs – Extra parameters to include for fetching access token.

Returns

A token dict.

fetch_request_token(url, realm=None, **kwargs)

Method for fetching an access token from the token endpoint.

This is the first step in the OAuth 1 workflow. A request token is obtained by making a signed post request to url. The token is then parsed from the application/x-www-form-urlencoded response and ready to be used to construct an authorization url.

Parameters
  • url – Request Token endpoint.

  • realm – A string/list/tuple of realm for Authorization header.

  • kwargs – Extra parameters to include for fetching token.

Returns

A Request Token dict.

Note, realm can also be configured when session created:

session = OAuth1Session(client_id, client_secret, ..., realm='')
parse_authorization_response(url)

Extract parameters from the post authorization redirect response URL.

Parameters

url – The full URL that resulted from the user being redirected back from the OAuth provider to you, the client.

Returns

A dict of parameters extracted from the URL.

class authlib.integrations.requests_client.OAuth1Auth(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', realm=None, force_include_body=False)

Signs the request using OAuth 1 (RFC5849)

class authlib.integrations.requests_client.OAuth2Session(client_id=None, client_secret=None, token_endpoint_auth_method=None, revocation_endpoint_auth_method=None, scope=None, state=None, redirect_uri=None, token=None, token_placement='header', update_token=None, **kwargs)

Construct a new OAuth 2 client requests session.

Parameters
  • client_id – Client ID, which you get from client registration.

  • client_secret – Client Secret, which you get from registration.

  • authorization_endpoint – URL of the authorization server’s authorization endpoint.

  • token_endpoint – URL of the authorization server’s token endpoint.

  • token_endpoint_auth_method – client authentication method for token endpoint.

  • revocation_endpoint – URL of the authorization server’s OAuth 2.0 revocation endpoint.

  • revocation_endpoint_auth_method – client authentication method for revocation endpoint.

  • scope – Scope that you needed to access user resources.

  • state – Shared secret to prevent CSRF attack.

  • redirect_uri – Redirect URI you registered as callback.

  • token – A dict of token attributes such as access_token, token_type and expires_at.

  • token_placement – The place to put token in HTTP request. Available values: “header”, “body”, “uri”.

  • update_token – A function for you to update token. It accept a OAuth2Token as parameter.

create_authorization_url(url, state=None, code_verifier=None, **kwargs)

Generate an authorization URL and state.

Parameters
  • url – Authorization endpoint url, must be HTTPS.

  • state – An optional state string for CSRF protection. If not given it will be generated for you.

  • code_verifier – An optional code_verifier for code challenge.

  • kwargs – Extra parameters to include.

Returns

authorization_url, state

fetch_token(url=None, body='', method='POST', headers=None, auth=None, grant_type=None, state=None, **kwargs)

Generic method for fetching an access token from the token endpoint.

Parameters
  • url – Access Token endpoint URL, if not configured, authorization_response is used to extract token from its fragment (implicit way).

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • method – The HTTP method used to make the request. Defaults to POST, but may also be GET. Other methods should be added as needed.

  • headers – Dict to default request headers with.

  • auth – An auth tuple or method as accepted by requests.

  • grant_type – Use specified grant_type to fetch token

Returns

A OAuth2Token object (a dict too).

introspect_token(url, token=None, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)

Implementation of OAuth 2.0 Token Introspection defined via RFC7662.

Parameters
  • url – Introspection Endpoint, must be HTTPS.

  • token – The token to be introspected.

  • token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

Introspection Response

refresh_token(url, refresh_token=None, body='', auth=None, headers=None, **kwargs)

Fetch a new access token using a refresh token.

Parameters
  • url – Refresh Token endpoint, must be HTTPS.

  • refresh_token – The refresh_token to use.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

A OAuth2Token object (a dict too).

register_client_auth_method(auth)

Extend client authenticate for token endpoint.

Parameters

auth – an instance to sign the request

register_compliance_hook(hook_type, hook)

Register a hook for request/response tweaking.

Available hooks are:

  • access_token_response: invoked before token parsing.

  • refresh_token_request: invoked before refreshing token.

  • refresh_token_response: invoked before refresh token parsing.

  • protected_request: invoked before making a request.

  • revoke_token_request: invoked before revoking a token.

  • introspect_token_request: invoked before introspecting a token.

revoke_token(url, token=None, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)

Revoke token method defined via RFC7009.

Parameters
  • url – Revoke Token endpoint, must be HTTPS.

  • token – The token to be revoked.

  • token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

Revocation Response

class authlib.integrations.requests_client.OAuth2Auth(token, token_placement='header', client=None)

Sign requests for OAuth 2.0, currently only bearer token is supported.

class authlib.integrations.requests_client.AssertionSession(token_endpoint, issuer, subject, audience=None, grant_type=None, claims=None, token_placement='header', scope=None, **kwargs)

Constructs a new Assertion Framework for OAuth 2.0 Authorization Grants per RFC7521.

HTTPX OAuth Clients
class authlib.integrations.httpx_client.OAuth1Auth(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', realm=None, force_include_body=False)

Signs the httpx request using OAuth 1 (RFC5849)

auth_flow(request: httpx.Request) Generator[httpx.Request, httpx.Response, None]

Execute the authentication flow.

To dispatch a request, yield it:

` yield request `

The client will .send() the response back into the flow generator. You can access it like so:

` response = yield request `

A return (or reaching the end of the generator) will result in the client returning the last response obtained from the server.

You can dispatch as many requests as is necessary.

class authlib.integrations.httpx_client.OAuth1Client(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', force_include_body=False, **kwargs)
create_authorization_url(url, request_token=None, **kwargs)

Create an authorization URL by appending request_token and optional kwargs to url.

This is the second step in the OAuth 1 workflow. The user should be redirected to this authorization URL, grant access to you, and then be redirected back to you. The redirection back can either be specified during client registration or by supplying a callback URI per request.

Parameters
  • url – The authorization endpoint URL.

  • request_token – The previously obtained request token.

  • kwargs – Optional parameters to append to the URL.

Returns

The authorization URL with new parameters embedded.

fetch_access_token(url, verifier=None, **kwargs)

Method for fetching an access token from the token endpoint.

This is the final step in the OAuth 1 workflow. An access token is obtained using all previously obtained credentials, including the verifier from the authorization step.

Parameters
  • url – Access Token endpoint.

  • verifier – A verifier string to prove authorization was granted.

  • kwargs – Extra parameters to include for fetching access token.

Returns

A token dict.

fetch_request_token(url, realm=None, **kwargs)

Method for fetching an access token from the token endpoint.

This is the first step in the OAuth 1 workflow. A request token is obtained by making a signed post request to url. The token is then parsed from the application/x-www-form-urlencoded response and ready to be used to construct an authorization url.

Parameters
  • url – Request Token endpoint.

  • realm – A string/list/tuple of realm for Authorization header.

  • kwargs – Extra parameters to include for fetching token.

Returns

A Request Token dict.

Note, realm can also be configured when session created:

session = OAuth1Session(client_id, client_secret, ..., realm='')
parse_authorization_response(url)

Extract parameters from the post authorization redirect response URL.

Parameters

url – The full URL that resulted from the user being redirected back from the OAuth provider to you, the client.

Returns

A dict of parameters extracted from the URL.

class authlib.integrations.httpx_client.AsyncOAuth1Client(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', force_include_body=False, **kwargs)
create_authorization_url(url, request_token=None, **kwargs)

Create an authorization URL by appending request_token and optional kwargs to url.

This is the second step in the OAuth 1 workflow. The user should be redirected to this authorization URL, grant access to you, and then be redirected back to you. The redirection back can either be specified during client registration or by supplying a callback URI per request.

Parameters
  • url – The authorization endpoint URL.

  • request_token – The previously obtained request token.

  • kwargs – Optional parameters to append to the URL.

Returns

The authorization URL with new parameters embedded.

async fetch_access_token(url, verifier=None, **kwargs)

Method for fetching an access token from the token endpoint.

This is the final step in the OAuth 1 workflow. An access token is obtained using all previously obtained credentials, including the verifier from the authorization step.

Parameters
  • url – Access Token endpoint.

  • verifier – A verifier string to prove authorization was granted.

  • kwargs – Extra parameters to include for fetching access token.

Returns

A token dict.

fetch_request_token(url, realm=None, **kwargs)

Method for fetching an access token from the token endpoint.

This is the first step in the OAuth 1 workflow. A request token is obtained by making a signed post request to url. The token is then parsed from the application/x-www-form-urlencoded response and ready to be used to construct an authorization url.

Parameters
  • url – Request Token endpoint.

  • realm – A string/list/tuple of realm for Authorization header.

  • kwargs – Extra parameters to include for fetching token.

Returns

A Request Token dict.

Note, realm can also be configured when session created:

session = OAuth1Session(client_id, client_secret, ..., realm='')
parse_authorization_response(url)

Extract parameters from the post authorization redirect response URL.

Parameters

url – The full URL that resulted from the user being redirected back from the OAuth provider to you, the client.

Returns

A dict of parameters extracted from the URL.

class authlib.integrations.httpx_client.OAuth2Auth(token, token_placement='header', client=None)

Sign requests for OAuth 2.0, currently only bearer token is supported.

class authlib.integrations.httpx_client.OAuth2Client(client_id=None, client_secret=None, token_endpoint_auth_method=None, revocation_endpoint_auth_method=None, scope=None, redirect_uri=None, token=None, token_placement='header', update_token=None, **kwargs)
create_authorization_url(url, state=None, code_verifier=None, **kwargs)

Generate an authorization URL and state.

Parameters
  • url – Authorization endpoint url, must be HTTPS.

  • state – An optional state string for CSRF protection. If not given it will be generated for you.

  • code_verifier – An optional code_verifier for code challenge.

  • kwargs – Extra parameters to include.

Returns

authorization_url, state

fetch_token(url=None, body='', method='POST', headers=None, auth=None, grant_type=None, state=None, **kwargs)

Generic method for fetching an access token from the token endpoint.

Parameters
  • url – Access Token endpoint URL, if not configured, authorization_response is used to extract token from its fragment (implicit way).

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • method – The HTTP method used to make the request. Defaults to POST, but may also be GET. Other methods should be added as needed.

  • headers – Dict to default request headers with.

  • auth – An auth tuple or method as accepted by requests.

  • grant_type – Use specified grant_type to fetch token

Returns

A OAuth2Token object (a dict too).

introspect_token(url, token=None, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)

Implementation of OAuth 2.0 Token Introspection defined via RFC7662.

Parameters
  • url – Introspection Endpoint, must be HTTPS.

  • token – The token to be introspected.

  • token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

Introspection Response

refresh_token(url, refresh_token=None, body='', auth=None, headers=None, **kwargs)

Fetch a new access token using a refresh token.

Parameters
  • url – Refresh Token endpoint, must be HTTPS.

  • refresh_token – The refresh_token to use.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

A OAuth2Token object (a dict too).

register_client_auth_method(auth)

Extend client authenticate for token endpoint.

Parameters

auth – an instance to sign the request

register_compliance_hook(hook_type, hook)

Register a hook for request/response tweaking.

Available hooks are:

  • access_token_response: invoked before token parsing.

  • refresh_token_request: invoked before refreshing token.

  • refresh_token_response: invoked before refresh token parsing.

  • protected_request: invoked before making a request.

  • revoke_token_request: invoked before revoking a token.

  • introspect_token_request: invoked before introspecting a token.

revoke_token(url, token=None, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)

Revoke token method defined via RFC7009.

Parameters
  • url – Revoke Token endpoint, must be HTTPS.

  • token – The token to be revoked.

  • token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

Revocation Response

class authlib.integrations.httpx_client.AsyncOAuth2Client(client_id=None, client_secret=None, token_endpoint_auth_method=None, revocation_endpoint_auth_method=None, scope=None, redirect_uri=None, token=None, token_placement='header', update_token=None, **kwargs)
create_authorization_url(url, state=None, code_verifier=None, **kwargs)

Generate an authorization URL and state.

Parameters
  • url – Authorization endpoint url, must be HTTPS.

  • state – An optional state string for CSRF protection. If not given it will be generated for you.

  • code_verifier – An optional code_verifier for code challenge.

  • kwargs – Extra parameters to include.

Returns

authorization_url, state

fetch_token(url=None, body='', method='POST', headers=None, auth=None, grant_type=None, state=None, **kwargs)

Generic method for fetching an access token from the token endpoint.

Parameters
  • url – Access Token endpoint URL, if not configured, authorization_response is used to extract token from its fragment (implicit way).

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • method – The HTTP method used to make the request. Defaults to POST, but may also be GET. Other methods should be added as needed.

  • headers – Dict to default request headers with.

  • auth – An auth tuple or method as accepted by requests.

  • grant_type – Use specified grant_type to fetch token

Returns

A OAuth2Token object (a dict too).

introspect_token(url, token=None, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)

Implementation of OAuth 2.0 Token Introspection defined via RFC7662.

Parameters
  • url – Introspection Endpoint, must be HTTPS.

  • token – The token to be introspected.

  • token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

Introspection Response

refresh_token(url, refresh_token=None, body='', auth=None, headers=None, **kwargs)

Fetch a new access token using a refresh token.

Parameters
  • url – Refresh Token endpoint, must be HTTPS.

  • refresh_token – The refresh_token to use.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

A OAuth2Token object (a dict too).

register_client_auth_method(auth)

Extend client authenticate for token endpoint.

Parameters

auth – an instance to sign the request

register_compliance_hook(hook_type, hook)

Register a hook for request/response tweaking.

Available hooks are:

  • access_token_response: invoked before token parsing.

  • refresh_token_request: invoked before refreshing token.

  • refresh_token_response: invoked before refresh token parsing.

  • protected_request: invoked before making a request.

  • revoke_token_request: invoked before revoking a token.

  • introspect_token_request: invoked before introspecting a token.

revoke_token(url, token=None, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)

Revoke token method defined via RFC7009.

Parameters
  • url – Revoke Token endpoint, must be HTTPS.

  • token – The token to be revoked.

  • token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.

  • body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.

  • auth – An auth tuple or method as accepted by requests.

  • headers – Dict to default request headers with.

Returns

Revocation Response

class authlib.integrations.httpx_client.AsyncAssertionClient(token_endpoint, issuer, subject, audience=None, grant_type=None, claims=None, token_placement='header', scope=None, **kwargs)
Flask Registry and RemoteApp
class authlib.integrations.flask_client.OAuth(app=None, cache=None, fetch_token=None, update_token=None)
create_client(name)

Create or get the given named OAuth client. For instance, the OAuth registry has .register a twitter client, developers may access the client with:

client = oauth.create_client('twitter')
Param

name: Name of the remote application

Returns

OAuth remote app

init_app(app, cache=None, fetch_token=None, update_token=None)

Initialize lazy for Flask app. This is usually used for Flask application factory pattern.

register(name, overwrite=False, **kwargs)

Registers a new remote application.

Parameters
  • name – Name of the remote application.

  • overwrite – Overwrite existing config with framework settings.

  • kwargs – Parameters for RemoteApp.

Find parameters for the given remote app class. When a remote app is registered, it can be accessed with named attribute:

oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
Django Registry and RemoteApp
class authlib.integrations.django_client.OAuth(cache=None, fetch_token=None, update_token=None)
create_client(name)

Create or get the given named OAuth client. For instance, the OAuth registry has .register a twitter client, developers may access the client with:

client = oauth.create_client('twitter')
Param

name: Name of the remote application

Returns

OAuth remote app

register(name, overwrite=False, **kwargs)

Registers a new remote application.

Parameters
  • name – Name of the remote application.

  • overwrite – Overwrite existing config with framework settings.

  • kwargs – Parameters for RemoteApp.

Find parameters for the given remote app class. When a remote app is registered, it can be accessed with named attribute:

oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
Starlette Registry and RemoteApp
class authlib.integrations.starlette_client.OAuth(config=None, cache=None, fetch_token=None, update_token=None)
create_client(name)

Create or get the given named OAuth client. For instance, the OAuth registry has .register a twitter client, developers may access the client with:

client = oauth.create_client('twitter')
Param

name: Name of the remote application

Returns

OAuth remote app

register(name, overwrite=False, **kwargs)

Registers a new remote application.

Parameters
  • name – Name of the remote application.

  • overwrite – Overwrite existing config with framework settings.

  • kwargs – Parameters for RemoteApp.

Find parameters for the given remote app class. When a remote app is registered, it can be accessed with named attribute:

oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')

JOSE Guide

This part of the documentation contains information on the JOSE implementation. It includes:

  1. JSON Web Signature (JWS)

  2. JSON Web Encryption (JWE)

  3. JSON Web Key (JWK)

  4. JSON Web Algorithm (JWA)

  5. JSON Web Token (JWT)

A simple example on how to use JWT with Authlib:

from authlib.jose import jwt

with open('private.pem', 'rb') as f:
    key = f.read()

payload = {'iss': 'Authlib', 'sub': '123', ...}
header = {'alg': 'RS256'}
s = jwt.encode(header, payload, key)

Follow the documentation below to find out more in detail.

JSON Web Signature (JWS)

JSON Web Signature (JWS) represents content secured with digital signatures or Message Authentication Codes (MACs) using JSON-based data structures.

There are two types of JWS Serializations:

  1. JWS Compact Serialization

  2. JWS JSON Serialization

The JWS Compact Serialization represents digitally signed or MACed content as a compact, URL-safe string. An example (with line breaks for display purposes only):

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

There are two types of JWS JSON Serialization syntax:

  1. General JWS JSON Serialization Syntax

  2. Flattened JWS JSON Serialization Syntax

An example on General JWS JSON Serialization Syntax (with line breaks within values for display purposes only):

{
  "payload":
    "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF
    tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
  "signatures":[
    {"protected":"eyJhbGciOiJSUzI1NiJ9",
     "header":{"kid":"2010-12-29"},
     "signature":
       "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZ
        mh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjb
        KBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHl
        b1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES
        c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AX
        LIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"},
    {"protected":"eyJhbGciOiJFUzI1NiJ9",
     "header":{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
     "signature":
       "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS
        lSApmWQxfKTUJqPP3-Kg6NU1Q"}]
}

An example on Flattened JWS JSON Serialization Syntax (with line breaks within values for display purposes only):

{
  "payload":
   "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF
    tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
  "protected":"eyJhbGciOiJFUzI1NiJ9",
  "header":
   {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
  "signature":
   "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS
    lSApmWQxfKTUJqPP3-Kg6NU1Q"
 }

A JWS requires JWA to work properly. The algorithms for JWS are provided in RFC7518: JSON Web Algorithms.

Compact Serialize and Deserialize

Generate a JWS compact serialization would be easy with JsonWebSignature.serialize_compact(), build a JWS instance with JWA:

from authlib.jose import JsonWebSignature

jws = JsonWebSignature()
# alg is a required parameter name
protected = {'alg': 'HS256'}
payload = b'example'
secret = b'secret'
jws.serialize_compact(protected, payload, secret)

There are other alg that you could use. Here is a full list of available algorithms:

  1. HS256, HS384, HS512

  2. RS256, RS384, RS512

  3. ES256, ES384, ES512, ES256K

  4. PS256, PS384, PS512

  5. EdDSA

For example, a JWS with RS256 requires a private PEM key to sign the JWS:

jws = JsonWebSignature(algorithms=['RS256'])
protected = {'alg': 'RS256'}
payload = b'example'
with open('private.pem', 'rb') as f:
    secret = f.read()
jws.serialize_compact(protected, payload, secret)

To deserialize a JWS Compact Serialization, use JsonWebSignature.deserialize_compact():

# if it is a RS256, we use public RSA key
with open('public.pem', 'rb') as f:
    key = f.read()
data = jws.deserialize_compact(s, key)
jws_header = data['header']
payload = data['payload']

Important

The above method is susceptible to a signature bypass described in CVE-2016-10555. It allows mixing symmetric algorithms and asymmetric algorithms. You should never combine symmetric (HS) and asymmetric (RS, ES, PS) signature schemes.

If you must support both protocols use a custom key loader which provides a different keys for different methods.

Load a different key for symmetric and asymmetric signatures:

def load_key(header, payload):
    if header['alg'] == 'RS256':
        return rsa_pub_key
    elif header['alg'] == 'HS256':
        return shared_secret
    else:
        raise UnsupportedAlgorithmError()

claims = jws.deserialize_compact(token, load_key)

A key can be dynamically loaded, if you don’t know which key to be used:

def load_key(header, payload):
    kid = header['kid']
    return get_key_by_kid(kid)

jws.deserialize_compact(s, load_key)

The result of the deserialize_compact is a dict, which contains header and payload. The value of the header is a JWSHeader.

Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).

JSON Serialize and Deserialize

JsonWebSignature.serialize_json() is used to generate a JWS JSON Serialization, JsonWebSignature.deserialize_json() is used to extract a JWS JSON Serialization. The usage is the same as “Compact Serialize and Deserialize”, the only difference is the “header”:

# Flattened JSON serialization header syntax
header = {'protected': {'alg': 'HS256'}, 'header': {'cty': 'JWT'}}
key = b'secret'
payload = b'example'
jws.serialize_json(header, payload, key)

# General JSON serialization header syntax
header = [{'protected': {'alg': 'HS256'}, 'header': {'cty': 'JWT'}}]
jws.serialize_json(header, payload, key)

For general JSON Serialization, there may be many signatures, each signature can use its own key, in this case the dynamical key would be useful:

def load_private_key(header, payload):
    kid = header['kid']
    return get_private_key(kid)

header = [
    {'protected': {'alg': 'HS256'}, 'header': {'kid': 'foo'}},
    {'protected': {'alg': 'RS256'}, 'header': {'kid': 'bar'}},
]
data = jws.serialize_json(header, payload, load_private_key)
# data is a dict

def load_public_key(header, payload):
    kid = header['kid']
    return get_public_key(kid)

jws.deserialize_json(data, load_public_key)

Actually, there is a JsonWebSignature.serialize() and JsonWebSignature.deserialize(), which can automatically serialize and deserialize Compact and JSON Serializations.

The result of the deserialize_json is a dict, which contains header and payload. The value of the header is a JWSHeader.

Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).

Header Parameter Names

JsonWebSignature has a validation on header parameter names. It will first check if the parameter name is in “Registered Header Parameter Names” defined by RFC7515 Section 4.1. Then it will check if the parameter name is in your defined private headers.

In this case, if there are header parameter names out of the registered header parameter names scope, you can pass the names:

private_headers = ['h1', 'h2']
jws = JsonWebSignature(private_headers=private_headers)

JSON Web Encryption (JWE)

JSON Web Encryption (JWE) represents encrypted content using JSON-based data structures.

There are two types of JWE Serializations:

  1. JWE Compact Serialization

  2. JWE JSON Serialization

Authlib has only implemented the Compact Serialization. This feature is not mature yet, use at your own risk.

The JWE Compact Serialization represents encrypted content as a compact, URL-safe string. This string is:

BASE64URL(UTF8(JWE Protected Header)) || ‘.’ || BASE64URL(JWE Encrypted Key) || ‘.’ || BASE64URL(JWE Initialization Vector) || ‘.’ || BASE64URL(JWE Ciphertext) || ‘.’ || BASE64URL(JWE Authentication Tag)

An example (with line breaks for display purposes only):

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ
.
OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe
ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb
Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV
mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8
1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi
6UklfCpIMfIjf7iGdXKHzg
.
48V1_ALb6US04U3b
.
5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji
SdiwkIr3ajwQzaBtQD_A
.
XFBoMYUZodetZdvTiFvSkQ

A JWE requires JWA to work properly. The algorithms for JWE are provided in RFC7518: JSON Web Algorithms.

Compact Serialize and Deserialize

Generate a JWE compact serialization would be easy with JsonWebEncryption.serialize_compact(), build a JWE instance with JWA:

from authlib.jose import JsonWebEncryption

jwe = JsonWebEncryption()
protected = {'alg': 'RSA-OAEP', 'enc': 'A256GCM'}
payload = b'hello'
with open('rsa_public.pem', 'rb') as f:
    key = f.read()

s = jwe.serialize_compact(protected, payload, key)

There are two required algorithms in protected header: alg and enc.

The available alg list:

  1. RSA1_5, RSA-OAEP, RSA-OAEP-256

  2. A128KW, A192KW, A256KW

  3. A128GCMKW, A192GCMKW, A256GCMKW

The available enc list:

  1. A128CBC-HS256, A192CBC-HS384, A256CBC-HS512

  2. A128GCM, A192GCM, A256GCM

More alg and enc will be added in the future.

It is also available to compress the payload with zip header:

protected = {'alg': 'RSA-OAEP', 'enc': 'A256GCM', 'zip': 'DEF'}
s = jwe.serialize_compact(protected, payload, key)

To deserialize a JWE Compact Serialization, use JsonWebEncryption.deserialize_compact():

with open('rsa_private.pem', 'rb') as f:
    key = f.read()

data = jwe.deserialize_compact(s, key)
jwe_header = data['header']
payload = data['payload']

The result of the deserialize_compact is a dict, which contains header and payload.

Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).

JSON Web Key (JWK)

Changed in version v0.15: This documentation is updated for v0.15. Please check “v0.14” documentation for Authlib v0.14.

A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. An example would help a lot:

{
  "kty": "EC",
  "crv": "P-256",
  "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
  "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
  "kid": "iss-a"
}

This is an Elliptic Curve Public Key represented by JSON data structure. JsonWebKey.import_key() will convert PEM, JSON, bytes into these keys:

  1. OctKey

  2. RSAKey

  3. ECKey

  4. OKPKey

Algorithms for kty (Key Type) is defined by RFC7518: JSON Web Algorithms. Import a key with:

from authlib.jose import JsonWebKey

key_data = read_file('public.pem')
key = JsonWebKey.import_key(key_data, {'kty': 'RSA'})

key.as_dict()
key.as_json()

You may pass extra parameters into import_key method, available parameters can be found on RFC7517 Section 4.

JSON Web Token (JWT)

JSON Web Token (JWT) is structured by RFC7515: JSON Web Signature or RFC7516: JSON Web Encryption with certain payload claims. The JWT implementation in Authlib has all built-in algorithms via RFC7518: JSON Web Algorithms, it can also load private/public keys of RFC7517: JSON Web Key:

>>> from authlib.jose import jwt
>>> header = {'alg': 'RS256'}
>>> payload = {'iss': 'Authlib', 'sub': '123', ...}
>>> key = read_file('private.pem')
>>> s = jwt.encode(header, payload, key)
>>> claims = jwt.decode(s, read_file('public.pem'))
>>> print(claims)
{'iss': 'Authlib', 'sub': '123', ...}
>>> print(claims.header)
{'alg': 'RS256', 'typ': 'JWT'}
>>> claims.validate()

The imported jwt is an instance of JsonWebToken. It has all supported JWS algorithms, and it can handle JWK automatically. When JsonWebToken.encode() a payload, JWT will check payload claims for security, if you really want to expose them, you can always turn it off via check=False.

Important

JWT payload with JWS is not encrypted, it is just signed. Anyone can extract the payload without any private or public keys. Adding sensitive data like passwords, social security numbers in JWT payload is not safe if you are going to send them in a non-secure connection.

You can also use JWT with JWE which is encrypted. But this feature is not mature, documentation is not provided yet.

JWT Encode

jwt.encode is the method to create a JSON Web Token string. It encodes the payload with the given alg in header:

>>> from authlib.jose import jwt
>>> header = {'alg': 'RS256'}
>>> payload = {'iss': 'Authlib', 'sub': '123', ...}
>>> key = read_file('private.pem')
>>> s = jwt.encode(header, payload, key)

The available keys in headers are defined by RFC7515: JSON Web Signature.

JWT Decode

jwt.decode is the method to translate a JSON Web Token string into the dict of the payload:

>>> from authlib.jose import jwt
>>> claims = jwt.decode(s, read_file('public.pem'))

Important

This decoding method is insecure. By default jwt.decode parses the alg header. This allows symmetric macs and asymmetric signatures. If both are allowed a signature bypass described in CVE-2016-10555 is possible.

See the following section for a mitigation.

The returned value is a JWTClaims, check the next section to validate claims value.

JWT with limited Algorithms

There are cases that we don’t want to support all the alg values, especially when decoding a token. In this case, we can pass a list of supported alg into JsonWebToken:

>>> from authlib.jose import JsonWebToken
>>> jwt = JsonWebToken(['RS256'])

Important

You should never combine symmetric (HS) and asymmetric (RS, ES, PS) signature schemes. When both are allowed a signature bypass described in CVE-2016-10555 is possible.

If you must support both protocols use a custom key loader which provides a different keys for different methods.

Load a different key for symmetric and asymmetric signatures:

def load_key(header, payload):
    if header['alg'] == 'RS256':
        return rsa_pub_key
    elif header['alg'] == 'HS256':
        return shared_secret
    else:
        raise UnsupportedAlgorithmError()

claims = jwt.decode(token, load_key)
JWT Payload Claims Validation

JsonWebToken.decode() accepts 3 claims-related parameters: claims_cls, claims_option and claims_params. The default claims_cls is JWTClaims. The decode method returns:

>>> JWTClaims(payload, header, options=claims_options, params=claims_params)

Claims validation is actually handled by JWTClaims.validate(), which validates payload claims with claims_option and claims_params. For standard JWTClaims, claims_params value is not used, but it is used in IDToken.

Here is an example of claims_option:

{
    "iss": {
        "essential": True,
        "values": ["https://example.com", "https://example.org"]
    },
    "sub": {
        "essential": True
        "value": "248289761001"
    },
    "jti": {
        "validate": validate_jti
    }
}

It is a dict configuration, the option key is the name of a claim.

  • essential: this value is REQUIRED.

  • values: claim value can be any one in the values list.

  • value: claim value MUST be the same value.

  • validate: a function to validate the claim value.

Use dynamic keys

When .encode and .decode a token, there is a key parameter to use. This key can be the bytes of your PEM key, a JWK set, and a function.

There ara cases that you don’t know which key to use to .decode the token. For instance, you have a JWK set:

jwks = {
  "keys": [
    { "kid": "k1", ...},
    { "kid": "k2", ...},
  ]
}

And in the token, it has a kid=k2 in the header part, if you pass jwks to the key parameter, Authlib will auto resolve the correct key:

jwt.decode(s, key=jwks, ...)

It is also possible to resolve the correct key by yourself:

def resolve_key(header, payload):
    return my_keys[header['kid']]

jwt.decode(s, key=resolve_key)

For .encode, if you pass a JWK set, it will randomly pick a key and assign its kid into the header.

OAuth & OpenID Connect

This section contains introduction and implementation of Authlib core OAuth 1.0, OAuth 2.0, and OpenID Connect.

OAuth 1.0

OAuth 1.0 is the standardization and combined wisdom of many well established industry protocols at its creation time. It was first introduced as Twitter’s open protocol. It is similar to other protocols at that time in use (Google AuthSub, AOL OpenAuth, Yahoo BBAuth, Upcoming API, Flickr API, etc).

If you are creating an open platform, AUTHLIB ENCOURAGE YOU USE OAUTH 2.0 INSTEAD.

Introduce OAuth 1.0

OAuth 1.0 is the standardization and combined wisdom of many well established industry protocols at its creation time. It was first introduced as Twitter’s open protocol. It is similar to other protocols at that time in use (Google AuthSub, AOL OpenAuth, Yahoo BBAuth, Upcoming API, Flickr API, etc).

Authlib implemented OAuth 1.0 according to RFC5849, this section will help developers understand the concepts in OAuth 1.0, the authorization flow of OAuth 1.0, and etc.

OAuth provides a method for clients to access server resources on behalf of a resource owner (such as a different client or an end- user). It also provides a process for end-users to authorize third- party access to their server resources without sharing their credentials (typically, a username and password pair), using user- agent redirection.

Here is an overview of a typical OAuth 1.0 authorization flow:

OAuth 1.0 Flow
OAuth 1.0 Flow

Let’s take your mobile Twitter app as an example. When a user wants to send a tweet through your application, he/she needs to authenticate at first. When the app is opened, and the login button is clicked:

  1. Client uses its client credentials to make a request to server, asking the server for a temporary credential.

  2. Server responds with a temporary credential if it verified your client credential.

  3. Client saves temporary credential for later use, then open a web view (browser) for resource owner to grant the access.

  4. When access is granted, Server responds with a verifier to client.

  5. Client uses this verifier and temporary credential to make a request to the server asking for token credentials.

  6. Server responds with access token if it verified everything.

And then Client can send tweets with the token credentials.

Roles in OAuth 1.0

To understand above flow, you need to know the roles in OAuth 1.0. There are usually three roles in an OAuth 1.0 flow. Take the above example, imagining that you are building a mobile app to send tweets:

  • Client: a client is a third-party application. In this case, it is your Twitter application.

  • Resource Owner: the users on Twitter are the resource owners, since they own their tweets (resources).

  • Server: authorization and resource server. In this case, it is Twitter.

OAuth 1.0 in HTTP

Let’s explain OAuth 1.0 in HTTP one more time. The first step is:

Client uses its client credentials to make a request to server, asking the server for a temporary credential.

It means we need to ask a temporary credential from Twitter. A temporary credential is called request token in Twitter. The first request is (line breaks are for display purposes only):

POST /oauth/request_token HTTP/1.1
Host: api.twitter.com
Authorization: OAuth
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131200",
    oauth_nonce="wIjqoS",
    oauth_callback="https%3A%2F%.example.com%2Fauth",
    oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D",
    oauth_version="1.0"

And Twitter will response with a temporary credential like:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik
&oauth_token_secret=Kd75W4OQfb2oJTV0vzGzeXftVAwgMnEK9MumzYcM
&oauth_callback_confirmed=true

Our Twitter client will then redirect user to the authorization page:

https://api.twitter.com/oauth/authenticate?oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik

On this authorization page, if user granted access to your Twitter client, it will redirect back to your application page, e.g.:

https://example.com/auth?oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik&oauth_verifier=hfdp7dh39dks9884

And the final step is here, use the temporary credential to exchange access token:

POST /oauth/access_token HTTP/1.1
Host: api.twitter.com
Authorization: OAuth
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_token="Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131201",
    oauth_nonce="walatlh",
    oauth_verifier="hfdp7dh39dks9884",
    oauth_signature=".....",
    oauth_version="1.0"

If everything works well, Twitter would response with the final access token now:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=6253282-eWudHldSbIaelX7swmsiHImEL4KinwaGloHANdrY
&oauth_token_secret=2EEfA6BG5ly3sR3XjE0IBSnlQu4ZrUzPiYTmrkVU
&user_id=6253282

You can use the oauth_token and oauth_token_secret for later use.

OAuth 2.0

Introduce OAuth 2.0

The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf.

This section will help developers understand the concepts in OAuth 2.0, but not in deep of OAuth 2.0. Here is an overview of a very simple OAuth 2.0 flow:

OAuth 2.0 Flow
OAuth 2.0 Roles

There are usually four roles in an OAuth 2.0 flow. Let’s take GitHub as an example, you are building an application to analyze one’s code on GitHub:

  • Client: a client is a third-party application, in this case, it is your application.

  • Resource Owner: the users and orgs on GitHub are the resource owners, since they own their source code (resources).

  • Resource Server: The API servers of GitHub. Your client will make requests to the resource server to fetch source code. The server serves resources.

  • Authorization Server: The server for client to obtain an access token.

OAuth 2.0 Flow

The above image is a simplified version of an OAuth 2.0 authorization. Let’s take GitHub as an example. A user wants to use your application to analyze his/her source code on GitHub.

It usually takes these steps:

  1. Your application (client) prompts the user to log in.

  2. The user clicks the login button, your application will redirect to GitHub’s authorize page (Authorization Server).

  3. The user (he/she is a GitHub user, which means he/she is a Resource Owner) clicks the allow button to tell GitHub that he/she granted the access.

  4. The Authorization Server issues an access token to your application. (This step can contain several sub-steps)

  5. Your application uses the access token to fetch source code from GitHub’s Resource Server, analyze the source code and return the result to your application user.

But there are more details inside the flow. The most important thing in OAuth 2.0 is the authorization. A client obtains an access token from the authorization server with the grant of the resource owner.

Grant Types

Authorization server MAY supports several grant types during the authorization, step 1 and 2. A grant type defines a way of how the authorization server will verify the request and issue the token.

There are lots of built-in grant types in Authlib, including:

Take authorization_code as an example, in step 2, when the resource owner granted the access, Authorization Server will return a code to the client. The client can use this code to exchange an access token:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
Client Authentication Methods

In the above code, there is an Authorization header; it contains the information of the client. A client MUST provide its client information to obtain an access token. There are several ways to provide this data, for instance:

  • none: The client is a public client which means it has no client_secret

    POST /token HTTP/1.1
    Host: server.example.com
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
    &client_id=s6BhdRkqt3
    
  • client_secret_post: The client uses the HTTP POST parameters

    POST /token HTTP/1.1
    Host: server.example.com
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
    &client_id=s6BhdRkqt3&client_secret=gX1fBat3bV
    
  • client_secret_basic: The client uses HTTP Basic Authorization

    POST /token HTTP/1.1
    Host: server.example.com
    Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
    

There are more client authentication methods defined by OAuth 2.0 extensions, including client_secret_jwt, private_key_jwt. They can be found in section Using JWTs for Client Authentication.

Token Scopes

Scope is a very important concept in OAuth 2.0. An access token is usually issued with limited scopes.

For instance, your “source code analyzer” application MAY only have access to the public repositories of a GiHub user.

Endpoints

The above example only shows one endpoint, which is token endpoint. There are more endpoints in OAuth 2.0. For example:

OpenID Connect

Introduce OpenID Connect

OpenID Connect is an identity layer on top of the OAuth 2.0 framework.

(TBD)

OpenID Connect Core

This section is about the core part of OpenID Connect. Authlib implemented OpenID Connect Core 1.0 on top of OAuth 2.0. It enhanced OAuth 2.0 with:

  1. OpenIDCode extension for Authorization code flow

  2. OpenIDImplicitGrant grant type for implicit flow

  3. OpenIDHybridGrant grant type for hybrid flow

Authorization Code Flow
Implicit Flow
Hybrid Flow
OpenID Connect Discovery

This section is about OpenID Provider Discovery. OpenID Providers have metadata describing their configuration. The endpoint is usually located at:

/.well-known/openid-configuration

The metadata is formatted in JSON. Here is an example of how it looks like:

HTTP/1.1 200 OK
Content-Type: application/json

{
 "issuer":
   "https://server.example.com",
 "authorization_endpoint":
   "https://server.example.com/connect/authorize",
 "token_endpoint":
   "https://server.example.com/connect/token",
 "token_endpoint_auth_methods_supported":
   ["client_secret_basic", "private_key_jwt"],
 "token_endpoint_auth_signing_alg_values_supported":
   ["RS256", "ES256"],
 "userinfo_endpoint":
   "https://server.example.com/connect/userinfo",
 "check_session_iframe":
   "https://server.example.com/connect/check_session",
 "end_session_endpoint":
   "https://server.example.com/connect/end_session",
 "jwks_uri":
   "https://server.example.com/jwks.json",
 "registration_endpoint":
   "https://server.example.com/connect/register",
 "scopes_supported":
   ["openid", "profile", "email", "address",
    "phone", "offline_access"],
 "response_types_supported":
   ["code", "code id_token", "id_token", "token id_token"],
 "acr_values_supported":
   ["urn:mace:incommon:iap:silver",
    "urn:mace:incommon:iap:bronze"],
 "subject_types_supported":
   ["public", "pairwise"],
 "userinfo_signing_alg_values_supported":
   ["RS256", "ES256", "HS256"],
 "userinfo_encryption_alg_values_supported":
   ["RSA1_5", "A128KW"],
 "userinfo_encryption_enc_values_supported":
   ["A128CBC-HS256", "A128GCM"],
 "id_token_signing_alg_values_supported":
   ["RS256", "ES256", "HS256"],
 "id_token_encryption_alg_values_supported":
   ["RSA1_5", "A128KW"],
 "id_token_encryption_enc_values_supported":
   ["A128CBC-HS256", "A128GCM"],
 "request_object_signing_alg_values_supported":
   ["none", "RS256", "ES256"],
 "display_values_supported":
   ["page", "popup"],
 "claim_types_supported":
   ["normal", "distributed"],
 "claims_supported":
   ["sub", "iss", "auth_time", "acr",
    "name", "given_name", "family_name", "nickname",
    "profile", "picture", "website",
    "email", "email_verified", "locale", "zoneinfo",
    "http://example.info/claims/groups"],
 "claims_parameter_supported":
   true,
 "service_documentation":
   "http://server.example.com/connect/service_documentation.html",
 "ui_locales_supported":
   ["en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"]
}

Flask OAuth Providers

Authlib has built-in Flask integrations for building OAuth 1.0, OAuth 2.0 and OpenID Connect servers. It is best if developers can read Introduce OAuth 1.0 and Introduce OAuth 2.0 at first.

Flask OAuth 1.0 Server

Implement OAuth 1.0 provider in Flask. An OAuth 1 provider contains two servers:

  • Authorization Server: to issue access tokens

  • Resources Server: to serve your users’ resources

At the very beginning, we need to have some basic understanding of the OAuth 1.0.

Important

If you are developing on your localhost, remember to set the environment variable:

export AUTHLIB_INSECURE_TRANSPORT=true

Looking for Flask OAuth 1.0 client? Check out Flask OAuth Client.

Authorization Server

The Authorization Server provides several endpoints for temporary credentials, authorization, and issuing token credentials. When the resource owner (user) grants the authorization, this server will issue a token credential to the client.

Changed in version v1.0.0: We have removed built-in SQLAlchemy integrations.

Resource Owner

Resource Owner is the user who is using your service. A resource owner can log in your website with username/email and password, or other methods.

A resource owner MUST implement get_user_id() method:

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)

    def get_user_id(self):
        return self.id
Client

A client is an application making protected resource requests on behalf of the resource owner and with its authorization. It contains at least three information:

  • Client Identifier, usually called client_id

  • Client Password, usually called client_secret

  • Client RSA Public Key (if RSA-SHA1 signature method supported)

Developers MUST implement the missing methods of authlib.oauth1.ClientMixin, take an example of Flask-SQAlchemy:

from authlib.oauth1 import ClientMixin

class Client(ClientMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    client_id = db.Column(db.String(48), index=True)
    client_secret = db.Column(db.String(120), nullable=False)
    default_redirect_uri = db.Column(db.Text, nullable=False, default='')
    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
    )
    user = db.relationship('User')

    def get_default_redirect_uri(self):
        return self.default_redirect_uri

    def get_client_secret(self):
        return self.client_secret

    def get_rsa_public_key(self):
        return None

A client is registered by a user (developer) on your website. Get a deep inside with ClientMixin API reference.

Temporary Credentials

A temporary credential is used to exchange a token credential. It is also known as “request token and secret”. Since it is temporary, it is better to save them into cache instead of database. A cache instance should have these methods:

  • .get(key)

  • .set(key, value, expires=None)

  • .delete(key)

A cache can be a memcache, redis or something else. If cache is not available, developers can also implement it with database. For example, using SQLAlchemy:

from authlib.oauth1 import TemporaryCredentialMixin

class TemporaryCredential(TemporaryCredentialMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
    )
    user = db.relationship('User')
    client_id = db.Column(db.String(48), index=True)
    oauth_token = db.Column(db.String(84), unique=True, index=True)
    oauth_token_secret = db.Column(db.String(84))
    oauth_verifier = db.Column(db.String(84))
    oauth_callback = db.Column(db.Text, default='')

    def get_client_id(self):
        return self.client_id

    def get_redirect_uri(self):
        return self.oauth_callback

    def check_verifier(self, verifier):
        return self.oauth_verifier == verifier

    def get_oauth_token(self):
        return self.oauth_token

    def get_oauth_token_secret(self):
        return self.oauth_token_secret
Token Credentials

A token credential is used to access resource owners’ resources. Unlike OAuth 2, the token credential will not expire in OAuth 1. This token credentials are supposed to be saved into a persist database rather than a cache.

Developers MUST implement TokenCredentialMixin missing methods. Here is an example of SQLAlchemy integration:

from authlib.oauth1 import TokenCredentialMixin

class TokenCredential(TokenCredentialMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
    )
    user = db.relationship('User')
    client_id = db.Column(db.String(48), index=True)
    oauth_token = db.Column(db.String(84), unique=True, index=True)
    oauth_token_secret = db.Column(db.String(84))

    def get_oauth_token(self):
        return self.oauth_token

    def get_oauth_token_secret(self):
        return self.oauth_token_secret
Timestamp and Nonce

The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations. Authlib Flask integration has a built-in validation with cache.

If cache is not available, developers can use a database, here is an example of using SQLAlchemy:

class TimestampNonce(db.Model):
    __table_args__ = (
        db.UniqueConstraint(
            'client_id', 'timestamp', 'nonce', 'oauth_token',
            name='unique_nonce'
        ),
    )
    id = db.Column(db.Integer, primary_key=True)
    client_id = db.Column(db.String(48), nullable=False)
    timestamp = db.Column(db.Integer, nullable=False)
    nonce = db.Column(db.String(48), nullable=False)
    oauth_token = db.Column(db.String(84))
Define A Server

Authlib provides a ready to use AuthorizationServer which has built-in tools to handle requests and responses:

from authlib.integrations.flask_oauth1 import AuthorizationServer

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

server = AuthorizationServer(app, query_client=query_client)

It can also be initialized lazily with init_app:

server = AuthorizationServer()
server.init_app(app, query_client=query_client)

It is strongly suggested that you use a cache. In this way, you don’t have to re-implement a lot of the missing methods.

There are other configurations. It works well without any changes. Here is a list of them:

OAUTH1_TOKEN_GENERATOR

A string of module path for importing a function to generate oauth_token

OAUTH1_TOKEN_SECRET_GENERATOR

A string of module path for importing a function to generate oauth_token_secret.

OAUTH1_TOKEN_LENGTH

If OAUTH1_TOKEN_GENERATOR is not configured, a random function will generate the given length of oauth_token. Default value is 42.

OAUTH1_TOKEN_SECRET_LENGTH

A random function will generate the given length of oauth_token_secret. Default value is 48.

These configurations are used to create the token_generator function. But you can pass the token_generator when initializing the AuthorizationServer:

def token_generator():
    return {
        'oauth_token': random_string(20),
        'oauth_token_secret': random_string(46)
    }

server = AuthorizationServer(
    app,
    query_client=query_client,
    token_generator=token_generator
)
Server Hooks

There are missing hooks that should be register_hook to AuthorizationServer. There are helper functions for registering hooks. If cache is available, you can take the advantage with:

from authlib.integrations.flask_oauth1.cache import (
    register_nonce_hooks,
    register_temporary_credential_hooks
)

register_nonce_hooks(server, cache)
register_temporary_credential_hooks(server, cache)

If cache is not available, developers MUST register the hooks with the database we defined above:

# check if nonce exists

def exists_nonce(nonce, timestamp, client_id, oauth_token):
    q = TimestampNonce.query.filter_by(
        nonce=nonce,
        timestamp=timestamp,
        client_id=client_id,
    )
    if oauth_token:
        q = q.filter_by(oauth_token=oauth_token)
    rv = q.first()
    if rv:
        return True

    item = TimestampNonce(
        nonce=nonce,
        timestamp=timestamp,
        client_id=client_id,
        oauth_token=oauth_token,
    )
    db.session.add(item)
    db.session.commit()
    return False
server.register_hook('exists_nonce', exists_nonce)

# hooks for temporary credential

def create_temporary_credential(token, client_id, redirect_uri):
    item = TemporaryCredential(
        client_id=client_id,
        oauth_token=token['oauth_token'],
        oauth_token_secret=token['oauth_token_secret'],
        oauth_callback=redirect_uri,
    )
    db.session.add(item)
    db.session.commit()
    return item

def get_temporary_credential(oauth_token):
    return TemporaryCredential.query.filter_by(oauth_token=oauth_token).first()

def delete_temporary_credential(oauth_token):
    q = TemporaryCredential.query.filter_by(oauth_token=oauth_token)
    q.delete(synchronize_session=False)
    db.session.commit()

def create_authorization_verifier(credential, grant_user, verifier):
    credential.user_id = grant_user.id  # assuming your end user model has `.id`
    credential.oauth_verifier = verifier
    db.session.add(credential)
    db.session.commit()
    return credential

server.register_hook('create_temporary_credential', create_temporary_credential)
server.register_hook('get_temporary_credential', get_temporary_credential)
server.register_hook('delete_temporary_credential', delete_temporary_credential)
server.register_hook('create_authorization_verifier', create_authorization_verifier)

For both cache and database temporary credential, Developers MUST register a create_token_credential hook:

def create_token_credential(token, temporary_credential):
    credential = TokenCredential(
        oauth_token=token['oauth_token'],
        oauth_token_secret=token['oauth_token_secret'],
        client_id=temporary_credential.get_client_id()
    )
    credential.user_id = temporary_credential.user_id
    db.session.add(credential)
    db.session.commit()
    return credential

server.register_hook('create_token_credential', create_token_credential)
Server Implementation

It is ready to create the endpoints for authorization and issuing tokens. Let’s start with the temporary credentials endpoint, which is used for clients to fetch a temporary credential:

@app.route('/initiate', methods=['POST'])
def initiate_temporary_credential():
    return server.create_temporary_credentials_response()

The endpoint for resource owner authorization. OAuth 1 Client will redirect user to this authorization page, so that resource owner can grant or deny this request:

@app.route('/authorize', methods=['GET', 'POST'])
def authorize():
    # make sure that user is logged in for yourself
    if request.method == 'GET':
        try:
            req = server.check_authorization_request()
            return render_template('authorize.html', req=req)
        except OAuth1Error as error:
            return render_template('error.html', error=error)

    granted = request.form.get('granted')
    if granted:
        grant_user = current_user
    else:
        grant_user = None

    try:
        return server.create_authorization_response(grant_user=grant_user)
    except OAuth1Error as error:
        return render_template('error.html', error=error)

Then the final token endpoint. OAuth 1 Client will use the given temporary credential and the oauth_verifier authorized by resource owner to exchange the token credential:

@app.route('/token', methods=['POST'])
def issue_token():
    return server.create_token_response()
Resource Servers

Changed in version v1.0.0: We have removed built-in SQLAlchemy integrations.

Protect users resources, so that only the authorized clients with the authorized access token can access the given scope resources.

A resource server can be a different server other than the authorization server. Here is the way to protect your users’ resources:

from flask import jsonify
from authlib.integrations.flask_oauth1 import ResourceProtector, current_credential

# we will define ``query_client``, ``query_token``, and ``exists_nonce`` later.
require_oauth = ResourceProtector(
    app, query_client=query_client,
    query_token=query_token,
    exists_nonce=exists_nonce,
)
# or initialize it lazily
require_oauth = ResourceProtector()
require_oauth.init_app(
    app,
    query_client=query_client,
    query_token=query_token,
    exists_nonce=exists_nonce,
)

@app.route('/user')
@require_oauth()
def user_profile():
    user = current_credential.user
    return jsonify(user)

The current_credential is a proxy to the Token model you have defined above. Since there is a user relationship on the Token model, we can access this user with current_credential.user.

Initialize

To initialize ResourceProtector, we need three functions:

  1. query_client

  2. query_token

  3. exists_nonce

If using SQLAlchemy, the query_client could be:

def query_client(client_id):
    # assuming ``Client`` is the model
    return Client.query.filter_by(client_id=client_id).first()

And query_token would be:

def query_token(client_id, oauth_token):
    return TokenCredential.query.filter_by(client_id=client_id, oauth_token=oauth_token).first()

For exists_nonce, if you are using cache now (as in authorization server), Authlib has a built-in tool function:

from authlib.integrations.flask_oauth1 import create_exists_nonce_func
exists_nonce = create_exists_nonce_func(cache)

If using database, with SQLAlchemy it would look like:

def exists_nonce(nonce, timestamp, client_id, oauth_token):
    q = db.session.query(TimestampNonce.nonce).filter_by(
        nonce=nonce,
        timestamp=timestamp,
        client_id=client_id,
    )
    if oauth_token:
        q = q.filter_by(oauth_token=oauth_token)
    rv = q.first()
    if rv:
        return True

    tn = TimestampNonce(
        nonce=nonce,
        timestamp=timestamp,
        client_id=client_id,
        oauth_token=oauth_token,
    )
    db.session.add(tn)
    db.session.commit()
    return False
MethodView & Flask-Restful

You can also use the require_oauth decorator in flask.views.MethodView and flask_restful.Resource:

from flask.views import MethodView

class UserAPI(MethodView):
    decorators = [require_oauth()]


from flask_restful import Resource

class UserAPI(Resource):
    method_decorators = [require_oauth()]
Customize Signature Methods

The AuthorizationServer and ResourceProtector only support HMAC-SHA1 signature method by default. There are three signature methods built-in, which can be enabled with the configuration:

OAUTH1_SUPPORTED_SIGNATURE_METHODS = ['HMAC-SHA1', 'PLAINTEXT', 'RSA-SHA1']

It is also possible to extend the signature methods. For example, you want to create a HMAC-SHA256 signature method:

import hmac
from authlib.common.encoding import to_bytes
from authlib.oauth1.rfc5849 import signature

def verify_hmac_sha256(request):
    text = signature.generate_signature_base_string(request)

    key = escape(request.client_secret or '')
    key += '&'
    key += escape(request.token_secret or '')

    sig = hmac.new(to_bytes(key), to_bytes(text), hashlib.sha256)
    return binascii.b2a_base64(sig.digest())[:-1]

AuthorizationServer.register_signature_method(
    'HMAC-SHA256', verify_hmac_sha256
)
ResourceProtector.register_signature_method(
    'HMAC-SHA256', verify_hmac_sha256
)

Then add this method into SUPPORTED_SIGNATURE_METHODS:

OAUTH1_SUPPORTED_SIGNATURE_METHODS = ['HMAC-SHA256']

With this configuration, your server will support HMAC-SHA256 signature method only. If you want to support more methods, add them to the list.

API References of Flask OAuth 1.0 Server

This part of the documentation covers the interface of Flask OAuth 1.0 Server.

class authlib.integrations.flask_oauth1.AuthorizationServer(app=None, query_client=None, token_generator=None)

Flask implementation of authlib.rfc5849.AuthorizationServer. Initialize it with Flask app instance, client model class and cache:

server = AuthorizationServer(app=app, query_client=query_client)
# or initialize lazily
server = AuthorizationServer()
server.init_app(app, query_client=query_client)
Parameters
  • app – A Flask app instance

  • query_client – A function to get client by client_id. The client model class MUST implement the methods described by ClientMixin.

  • token_generator – A function to generate token

create_authorization_response(request=None, grant_user=None)

Validate authorization request and create authorization response. Assume the endpoint for authorization request is https://photos.example.net/authorize, the client redirects Jane’s user-agent to the server’s Resource Owner Authorization endpoint to obtain Jane’s approval for accessing her private photos:

https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola

The server requests Jane to sign in using her username and password and if successful, asks her to approve granting ‘printer.example.com’ access to her private photos. Jane approves the request and her user-agent is redirected to the callback URI provided by the client in the previous request (line breaks are for display purposes only):

http://printer.example.com/ready?
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884
Parameters
  • request – OAuth1Request instance.

  • grant_user – if granted, pass the grant user, otherwise None.

Returns

(status_code, body, headers)

create_authorization_verifier(request)

Create and bind oauth_verifier to temporary credential. It could be re-implemented in this way:

def create_authorization_verifier(self, request):
    verifier = generate_token(36)

    temporary_credential = request.credential
    user_id = request.user.id

    temporary_credential.user_id = user_id
    temporary_credential.oauth_verifier = verifier
    # if the credential has a save method
    temporary_credential.save()

    # remember to return the verifier
    return verifier
Parameters

request – OAuth1Request instance

Returns

A string of oauth_verifier

create_temporary_credential(request)

Generate and save a temporary credential into database or cache. A temporary credential is used for exchanging token credential. This method should be re-implemented:

def create_temporary_credential(self, request):
    oauth_token = generate_token(36)
    oauth_token_secret = generate_token(48)
    temporary_credential = TemporaryCredential(
        oauth_token=oauth_token,
        oauth_token_secret=oauth_token_secret,
        client_id=request.client_id,
        redirect_uri=request.redirect_uri,
    )
    # if the credential has a save method
    temporary_credential.save()
    return temporary_credential
Parameters

request – OAuth1Request instance

Returns

TemporaryCredential instance

create_temporary_credentials_response(request=None)

Validate temporary credentials token request and create response for temporary credentials token. Assume the endpoint of temporary credentials request is https://photos.example.net/initiate:

POST /initiate HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131200",
    oauth_nonce="wIjqoS",
    oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
    oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

The server validates the request and replies with a set of temporary credentials in the body of the HTTP response:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&
oauth_callback_confirmed=true
Parameters

request – OAuth1Request instance.

Returns

(status_code, body, headers)

create_token_credential(request)

Create and save token credential into database. This method would be re-implemented like this:

def create_token_credential(self, request):
    oauth_token = generate_token(36)
    oauth_token_secret = generate_token(48)
    temporary_credential = request.credential

    token_credential = TokenCredential(
        oauth_token=oauth_token,
        oauth_token_secret=oauth_token_secret,
        client_id=temporary_credential.get_client_id(),
        user_id=temporary_credential.get_user_id()
    )
    # if the credential has a save method
    token_credential.save()
    return token_credential
Parameters

request – OAuth1Request instance

Returns

TokenCredential instance

create_token_response(request=None)

Validate token request and create token response. Assuming the endpoint of token request is https://photos.example.net/token, the callback request informs the client that Jane completed the authorization process. The client then requests a set of token credentials using its temporary credentials (over a secure Transport Layer Security (TLS) channel):

POST /token HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_token="hh5s93j4hdidpola",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131201",
    oauth_nonce="walatlh",
    oauth_verifier="hfdp7dh39dks9884",
    oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

The server validates the request and replies with a set of token credentials in the body of the HTTP response:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00
Parameters

request – OAuth1Request instance.

Returns

(status_code, body, headers)

delete_temporary_credential(request)

Delete temporary credential from database or cache. For instance, if temporary credential is saved in cache:

def delete_temporary_credential(self, request):
    key = 'a-key-prefix:{}'.format(request.token)
    cache.delete(key)
Parameters

request – OAuth1Request instance

exists_nonce(nonce, request)

The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.

Parameters
  • nonce – A string value of oauth_nonce

  • request – OAuth1Request instance

Returns

Boolean

get_client_by_id(client_id)

Get client instance with the given client_id.

Parameters

client_id – A string of client_id

Returns

Client instance

get_temporary_credential(request)

Get the temporary credential from database or cache. A temporary credential should share the same methods as described in models of TemporaryCredentialMixin:

def get_temporary_credential(self, request):
    key = 'a-key-prefix:{}'.format(request.token)
    data = cache.get(key)
    # TemporaryCredential shares methods from TemporaryCredentialMixin
    return TemporaryCredential(data)
Parameters

request – OAuth1Request instance

Returns

TemporaryCredential instance

class authlib.integrations.flask_oauth1.ResourceProtector(app=None, query_client=None, query_token=None, exists_nonce=None)

A protecting method for resource servers. Initialize a resource protector with the these method:

  1. query_client

  2. query_token,

  3. exists_nonce

Usually, a query_client method would look like (if using SQLAlchemy):

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

A query_token method accept two parameters, client_id and oauth_token:

def query_token(client_id, oauth_token):
    return Token.query.filter_by(client_id=client_id, oauth_token=oauth_token).first()

And for exists_nonce, if using cache, we have a built-in hook to create this method:

from authlib.integrations.flask_oauth1 import create_exists_nonce_func

exists_nonce = create_exists_nonce_func(cache)

Then initialize the resource protector with those methods:

require_oauth = ResourceProtector(
    app, query_client=query_client,
    query_token=query_token, exists_nonce=exists_nonce,
)
get_client_by_id(client_id)

Get client instance with the given client_id.

Parameters

client_id – A string of client_id

Returns

Client instance

get_token_credential(request)

Fetch the token credential from data store like a database, framework should implement this function.

Parameters

request – OAuth1Request instance

Returns

Token model instance

exists_nonce(nonce, request)

The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.

Parameters
  • nonce – A string value of oauth_nonce

  • request – OAuth1Request instance

Returns

Boolean

authlib.integrations.flask_oauth1.current_credential

Routes protected by ResourceProtector can access current credential with this variable.

Flask OAuth 2.0 Server

This section is not a step by step guide on how to create an OAuth 2.0 provider in Flask. Instead, we will learn how the Flask implementation works, and some technical details in an OAuth 2.0 provider.

If you need a quick example, here are the official tutorial guide and examples on GitHub:

  1. Example of OAuth 2.0 server

  2. Example of OpenID Connect server

  3. On Demand Demo for your business

At the very beginning, we need to have some basic understanding of the OAuth 2.0.

Important

If you are developing on your localhost, remember to set the environment variable:

export AUTHLIB_INSECURE_TRANSPORT=true

Looking for Flask OAuth 2.0 client? Check out Flask OAuth Client.

Authorization Server

The Authorization Server provides several endpoints for authorization, issuing tokens, refreshing tokens and revoking tokens. When the resource owner (user) grants the authorization, this server will issue an access token to the client.

Before creating the authorization server, we need to understand several concepts:

Resource Owner

Resource Owner is the user who is using your service. A resource owner can log in your website with username/email and password, or other methods.

A resource owner SHOULD implement get_user_id() method, lets take SQLAlchemy models for example:

class User(Model):
    id = Column(Integer, primary_key=True)
    # other columns

    def get_user_id(self):
        return self.id
Client

A client is an application making protected resource requests on behalf of the resource owner and with its authorization. It contains at least three information:

  • Client Identifier, usually called client_id

  • Client Password, usually called client_secret

  • Client Token Endpoint Authentication Method

Authlib has provided a mixin for SQLAlchemy, define the client with this mixin:

from authlib.integrations.sqla_oauth2 import OAuth2ClientMixin

class Client(Model, OAuth2ClientMixin):
    id = Column(Integer, primary_key=True)
    user_id = Column(
        Integer, ForeignKey('user.id', ondelete='CASCADE')
    )
    user = relationship('User')

A client is registered by a user (developer) on your website. If you decide to implement all the missing methods by yourself, get a deep inside with ClientMixin API reference.

Token

Note

Only Bearer Token is supported for now. MAC Token is still under draft, it will be available when it goes into RFC.

Tokens are used to access the users’ resources. A token is issued with a valid duration, limited scopes and etc. It contains at least:

  • access_token: a token to authorize the http requests.

  • refresh_token: (optional) a token to exchange a new access token

  • client_id: this token is issued to which client

  • expires_at: when will this token expired

  • scope: a limited scope of resources that this token can access

With the SQLAlchemy mixin provided by Authlib:

from authlib.integrations.sqla_oauth2 import OAuth2TokenMixin

class Token(db.Model, OAuth2TokenMixin):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
    )
    user = db.relationship('User')

A token is associated with a resource owner. There is no certain name for it, here we call it user, but it can be anything else.

If you decide to implement all the missing methods by yourself, get a deep inside the TokenMixin API reference.

Server

Authlib provides a ready to use AuthorizationServer which has built-in tools to handle requests and responses:

from authlib.integrations.flask_oauth2 import AuthorizationServer

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

def save_token(token_data, request):
    if request.user:
        user_id = request.user.get_user_id()
    else:
        # client_credentials grant_type
        user_id = request.client.user_id
        # or, depending on how you treat client_credentials
        user_id = None
    token = Token(
        client_id=request.client.client_id,
        user_id=user_id,
        **token_data
    )
    db.session.add(token)
    db.session.commit()

# or with the helper
from authlib.integrations.sqla_oauth2 import (
    create_query_client_func,
    create_save_token_func
)
query_client = create_query_client_func(db.session, Client)
save_token = create_save_token_func(db.session, Token)

server = AuthorizationServer(
    app, query_client=query_client, save_token=save_token
)

It can also be initialized lazily with init_app:

server = AuthorizationServer()
server.init_app(app, query_client=query_client, save_token=save_token)

It works well without configuration. However, it can be configured with these settings:

OAUTH2_TOKEN_EXPIRES_IN

A dict to define expires_in for each grant

OAUTH2_ACCESS_TOKEN_GENERATOR

A function or string of module path for importing a function to generate access_token

OAUTH2_REFRESH_TOKEN_GENERATOR

A function or string of module path for importing a function to generate refresh_token. It can also be True/False

OAUTH2_ERROR_URIS

A list of tuple for (error, error_uri)

Hint

Here is an example of OAUTH2_TOKEN_EXPIRES_IN:

OAUTH2_TOKEN_EXPIRES_IN = {
    'authorization_code': 864000,
    'implicit': 3600,
    'password': 864000,
    'client_credentials': 864000
}

Here is an example of OAUTH2_ACCESS_TOKEN_GENERATOR:

def gen_access_token(client, grant_type, user, scope):
    return create_some_random_string()

OAUTH2_REFRESH_TOKEN_GENERATOR accepts the same parameters.

Now define an endpoint for authorization. This endpoint is used by authorization_code and implicit grants:

from flask import request, render_template
from your_project.auth import current_user

@app.route('/oauth/authorize', methods=['GET', 'POST'])
def authorize():
    # Login is required since we need to know the current resource owner.
    # It can be done with a redirection to the login page, or a login
    # form on this authorization page.
    if request.method == 'GET':
        grant = server.get_consent_grant(end_user=current_user)
        client = grant.client
        scope = client.get_allowed_scope(grant.request.scope)

        # You may add a function to extract scope into a list of scopes
        # with rich information, e.g.
        scopes = describe_scope(scope)  # returns [{'key': 'email', 'icon': '...'}]
        return render_template(
            'authorize.html',
            grant=grant,
            user=current_user,
            client=client,
            scopes=scopes,
        )
    confirmed = request.form['confirm']
    if confirmed:
        # granted by resource owner
        return server.create_authorization_response(grant_user=current_user)
    # denied by resource owner
    return server.create_authorization_response(grant_user=None)

This is a simple demo, the real case should be more complex. There is a little more complex demo in https://github.com/authlib/example-oauth2-server.

The token endpoint is much easier:

@app.route('/oauth/token', methods=['POST'])
def issue_token():
    return server.create_token_response()

However, the routes will not work properly. We need to register supported grants for them.

Register Error URIs

To create a better developer experience for debugging, it is suggested that you create some documentation for errors. Here is a list of built-in Token Model.

You can design a documentation page with a description of each error. For instance, there is a web page for invalid_client:

https://developer.your-company.com/errors#invalid-client

In this case, you can register the error URI with OAUTH2_ERROR_URIS configuration:

OAUTH2_ERROR_URIS = [
   ('invalid_client', 'https://developer.your-company.com/errors#invalid-client'),
   # other error URIs
]

If there is no OAUTH2_ERROR_URIS, the error response will not contain any error_uri data.

I18N on Errors

It is also possible to add i18n support to the error_description. The feature has been implemented in version 0.8, but there is still work to do.

Register Grants

There are four grant types defined by RFC6749, you can also create your own extended grant. Register the supported grant types to the authorization server.

Authorization Code Grant

Authorization Code Grant is a very common grant type, it is supported by almost every OAuth 2 providers. It uses an authorization code to exchange access tokens. In this case, we need a place to store the authorization code. It can be kept in a database or a cache like redis. Here is a SQLAlchemy mixin for AuthorizationCode:

from authlib.integrations.sqla_oauth2 import OAuth2AuthorizationCodeMixin

class AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
    )
    user = db.relationship('User')

Implement this grant by subclassing AuthorizationCodeGrant:

from authlib.oauth2.rfc6749 import grants

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    def save_authorization_code(self, code, request):
        client = request.client
        auth_code = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=request.user.id,
        )
        db.session.add(auth_code)
        db.session.commit()
        return auth_code

    def query_authorization_code(self, code, client):
        item = AuthorizationCode.query.filter_by(
            code=code, client_id=client.client_id).first()
        if item and not item.is_expired():
            return item

    def delete_authorization_code(self, authorization_code):
        db.session.delete(authorization_code)
        db.session.commit()

    def authenticate_user(self, authorization_code):
        return User.query.get(authorization_code.user_id)

# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant)

Note

AuthorizationCodeGrant is the most complex grant.

Default allowed Client Authentication Methods are:

  1. client_secret_basic

  2. client_secret_post

  3. none

You can change it in the subclass, e.g. remove the none authentication method:

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post']

Note

This is important when you want to support OpenID Connect.

Implicit Grant

The implicit grant type is usually used in a browser, when resource owner granted the access, an access token is issued in the redirect URI, there is no missing implementation, which means it can be easily registered with:

from authlib.oauth2.rfc6749 import grants

# register it to grant endpoint
server.register_grant(grants.ImplicitGrant)

Implicit Grant is used by public clients which have no client_secret. Default allowed Client Authentication Methods: none.

Resource Owner Password Credentials Grant

The resource owner uses its username and password to exchange an access token. This grant type should be used only when the client is trustworthy; implement it with a subclass of ResourceOwnerPasswordCredentialsGrant:

from authlib.oauth2.rfc6749 import grants

class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
    def authenticate_user(self, username, password):
        user = User.query.filter_by(username=username).first()
        if user.check_password(password):
            return user

# register it to grant endpoint
server.register_grant(PasswordGrant)

Default allowed Client Authentication Methods: client_secret_basic. You can add more in the subclass:

class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = [
        'client_secret_basic', 'client_secret_post'
    ]
Client Credentials Grant

Client credentials grant type can access public resources and MAYBE the client’s creator’s resources, depending on how you issue tokens to this grant type. It can be easily registered with:

from authlib.oauth2.rfc6749 import grants

# register it to grant endpoint
server.register_grant(grants.ClientCredentialsGrant)

Default allowed Client Authentication Methods: client_secret_basic. You can add more in the subclass:

class ClientCredentialsGrant(grants.ClientCredentialsGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = [
        'client_secret_basic', 'client_secret_post'
    ]
Refresh Token Grant

Many OAuth 2 providers do not implement a refresh token endpoint. Authlib provides it as a grant type; implement it with a subclass of RefreshTokenGrant:

from authlib.oauth2.rfc6749 import grants

class RefreshTokenGrant(grants.RefreshTokenGrant):
    def authenticate_refresh_token(self, refresh_token):
        item = Token.query.filter_by(refresh_token=refresh_token).first()
        # define is_refresh_token_valid by yourself
        # usually, you should check if refresh token is expired and revoked
        if item and item.is_refresh_token_valid():
            return item

    def authenticate_user(self, credential):
        return User.query.get(credential.user_id)

    def revoke_old_credential(self, credential):
        credential.revoked = True
        db.session.add(credential)
        db.session.commit()

# register it to grant endpoint
server.register_grant(RefreshTokenGrant)

Default allowed Client Authentication Methods: client_secret_basic. You can add more in the subclass:

class RefreshTokenGrant(grants.RefreshTokenGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = [
        'client_secret_basic', 'client_secret_post'
    ]

By default, RefreshTokenGrant will not issue a refresh_token in the token response. Developers can change this behavior with:

class RefreshTokenGrant(grants.RefreshTokenGrant):
    INCLUDE_NEW_REFRESH_TOKEN = True
Custom Grant Types

It is also possible to create your own grant types. In Authlib, a Grant supports two endpoints:

  1. Authorization Endpoint: which can handle requests with response_type.

  2. Token Endpoint: which is the endpoint to issue tokens.

Changed in version v0.12: Using AuthorizationEndpointMixin and TokenEndpointMixin instead of AUTHORIZATION_ENDPOINT=True and TOKEN_ENDPOINT=True.

Creating a custom grant type with BaseGrant:

from authlib.oauth2.rfc6749.grants import (
    BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin
)


class MyCustomGrant(BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin):
    GRANT_TYPE = 'custom-grant-type-name'

    def validate_authorization_request(self):
        # only needed if using AuthorizationEndpointMixin

    def create_authorization_response(self, grant_user):
        # only needed if using AuthorizationEndpointMixin

    def validate_token_request(self):
        # only needed if using TokenEndpointMixin

    def create_token_response(self):
        # only needed if using TokenEndpointMixin

For a better understanding, you can read the source code of the built-in grant types. And there are extended grant types defined by other specs:

  1. Using JWTs as Authorization Grants

Grant Extensions

New in version 0.10.

Grants can accept extensions. Developers can pass extensions when registering grants:

authorization_server.register_grant(AuthorizationCodeGrant, [extension])

For instance, there is the CodeChallenge extension in Authlib:

server.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=False)])

Learn more about CodeChallenge at RFC7636: Proof Key for Code Exchange by OAuth Public Clients.

Token Endpoints

Flask OAuth 2.0 authorization server has a method to register other token endpoints: authorization_server.register_endpoint. Find the available endpoints:

Resource Server

Protects users resources, so that only the authorized clients with the authorized access token can access the given scope resources.

A resource server can be a different server other than the authorization server. Authlib offers a decorator to protect your API endpoints:

from flask import jsonify
from authlib.integrations.flask_oauth2 import ResourceProtector, current_token
from authlib.oauth2.rfc6750 import BearerTokenValidator

class MyBearerTokenValidator(BearerTokenValidator):
    def authenticate_token(self, token_string):
        return Token.query.filter_by(access_token=token_string).first()

require_oauth = ResourceProtector()

# only bearer token is supported currently
require_oauth.register_token_validator(MyBearerTokenValidator())

When the resource server has no access to the Token model (database), and there is an introspection token endpoint in authorization server, you can Use Introspection in Resource Server.

Here is the way to protect your users’ resources:

@app.route('/user')
@require_oauth('profile')
def user_profile():
    # if Token model has `.user` foreign key
    user = current_token.user
    return jsonify(user)

If the resource is not protected by a scope, use None:

@app.route('/user')
@require_oauth()
def user_profile():
    user = current_token.user
    return jsonify(user)

# or with None

@app.route('/user')
@require_oauth(None)
def user_profile():
    user = current_token.user
    return jsonify(user)

The current_token is a proxy to the Token model you have defined above. Since there is a user relationship on the Token model, we can access this user with current_token.user.

If the decorator is not your favorite, there is a with statement for you:

@app.route('/user')
def user_profile():
    with require_oauth.acquire('profile') as token:
        user = token.user
        return jsonify(user)
Multiple Scopes

Changed in version v1.0.

You can apply multiple scopes to one endpoint in AND, OR and mix modes. Here are some examples:

@app.route('/profile')
@require_oauth(['profile email'])
def user_profile():
    user = current_token.user
    return jsonify(user)

It requires the token containing both profile and email scope.

@app.route('/profile')
@require_oauth(['profile', 'email']')
def user_profile():
    user = current_token.user
    return jsonify(user)

It requires the token containing either profile or email scope.

It is also possible to mix AND and OR logic. e.g.:

@app.route('/profile')
@require_oauth(['profile email', 'user'])
def user_profile():
    user = current_token.user
    return jsonify(user)

This means if the token will be valid if:

  1. token contains both profile and email scope

  2. or token contains user scope

Optional require_oauth

There is one more parameter for require_oauth which is used to serve public endpoints:

@app.route('/timeline')
@require_oauth(optional=True)
def timeline_api():
    if current_token:
        return get_user_timeline(current_token.user)
    return get_public_timeline()
MethodView & Flask-Restful

You can also use the require_oauth decorator in flask.views.MethodView and flask_restful.Resource:

from flask.views import MethodView

class UserAPI(MethodView):
    decorators = [require_oauth('profile')]


from flask_restful import Resource

class UserAPI(Resource):
    method_decorators = [require_oauth('profile')]
Flask OIDC Provider

OpenID Connect 1.0 is supported since version 0.6. The integrations are built with Custom Grant Types and Grant Extensions. Since OpenID Connect is built on OAuth 2.0 frameworks, you need to read Flask OAuth 2.0 Server at first.

Changed in version v0.12: The Grant system has been redesigned from v0.12. This documentation ONLY works for Authlib >=v0.12.

Looking for OpenID Connect Client? Head over to Flask OAuth Client.

Understand JWT

OpenID Connect 1.0 uses JWT a lot. Make sure you have the basic understanding of JOSE Guide.

For OpenID Connect, we need to understand at least four concepts:

  1. alg: Algorithm for JWT

  2. key: Private key for JWT

  3. iss: Issuer value for JWT

  4. exp: JWT expires time

alg

The algorithm to sign a JWT. This is the alg value defined in header part of a JWS:

{"alg": "RS256"}

The available algorithms are defined in RFC7518: JSON Web Algorithms, which are:

  • HS256: HMAC using SHA-256

  • HS384: HMAC using SHA-384

  • HS512: HMAC using SHA-512

  • RS256: RSASSA-PKCS1-v1_5 using SHA-256

  • RS384: RSASSA-PKCS1-v1_5 using SHA-384

  • RS512: RSASSA-PKCS1-v1_5 using SHA-512

  • ES256: ECDSA using P-256 and SHA-256

  • ES384: ECDSA using P-384 and SHA-384

  • ES512: ECDSA using P-521 and SHA-512

  • PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256

  • PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384

  • PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512

The HMAC using SHA algorithms are not suggested since you need to share secrets between server and client. Most OpenID Connect services are using RS256.

key

A private key is required to generate a JWT. The key that you are going to use dependents on the alg you are using. For instance, the alg is RS256, you need to use an RSA private key. It can be set with:

key = '''-----BEGIN RSA PRIVATE KEY-----\nMIIEog...'''

# or in JWK format
key = {"kty": "RSA", "n": ...}
iss

The iss value in the JWT payload. The value can be your website name or URL. For example, Google is using:

{"iss": "https://accounts.google.com"}
Code Flow

OpenID Connect authorization code flow relies on the OAuth2 authorization code flow and extends it.

OpenID Connect Code flow is the same as Authorization Code flow, but with extended features. We can apply the OpenIDCode extension to Authorization Code Grant.

First, we need to implement the missing methods for OpenIDCode:

from authlib.oidc.core import grants, UserInfo

class OpenIDCode(grants.OpenIDCode):
    def exists_nonce(self, nonce, request):
        exists = AuthorizationCode.query.filter_by(
            client_id=request.client_id, nonce=nonce
        ).first()
        return bool(exists)

    def get_jwt_config(self, grant):
        return {
            'key': read_private_key_file(key_path),
            'alg': 'RS512',
            'iss': 'https://example.com',
            'exp': 3600
        }

    def generate_user_info(self, user, scope):
        user_info = UserInfo(sub=user.id, name=user.name)
        if 'email' in scope:
            user_info['email'] = user.email
        return user_info

Second, since there is one more nonce value in the AuthorizationCode data, we need to save this value into the database. In this case, we have to update our Authorization Code Grant save_authorization_code method:

class AuthorizationCodeGrant(_AuthorizationCodeGrant):
    def save_authorization_code(self, code, request):
        # openid request MAY have "nonce" parameter
        nonce = request.data.get('nonce')
        auth_code = AuthorizationCode(
            code=code,
            client_id=request.client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=request.user.id,
            nonce=nonce,
        )
        db.session.add(auth_code)
        db.session.commit()
        return auth_code

    # ...

Finally, you can register AuthorizationCodeGrant with the OpenIDCode extension:

# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant, [OpenIDCode(require_nonce=True)])

The difference between OpenID Code flow and the standard code flow is that OpenID Connect requests have a scope of “openid”:

GET /authorize?
response_type=code
&scope=openid%20profile%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1
Host: server.example.com

With the example above, you will also have to change the scope of your client in your application to something like openid profile email.

Now that you added the openid scope to your application, an OpenID token will be provided to this app whenever a client asks for a token with an openid scope.

Implicit Flow

The Implicit Flow is mainly used by Clients implemented in a browser using a scripting language. You need to implement the missing methods of OpenIDImplicitGrant before registering it:

from authlib.oidc.core import grants

class OpenIDImplicitGrant(grants.OpenIDImplicitGrant):
    def exists_nonce(self, nonce, request):
        exists = AuthorizationCode.query.filter_by(
            client_id=request.client_id, nonce=nonce
        ).first()
        return bool(exists)

    def get_jwt_config(self):
        return {
            'key': read_private_key_file(key_path),
            'alg': 'RS512',
            'iss': 'https://example.com',
            'exp': 3600
        }

    def generate_user_info(self, user, scope):
        user_info = UserInfo(sub=user.id, name=user.name)
        if 'email' in scope:
            user_info['email'] = user.email
        return user_info

server.register_grant(OpenIDImplicitGrant)
Hybrid Flow

The Hybrid flow is a mix of code flow and implicit flow. You only need to implement the authorization endpoint part, as token endpoint will be handled by Authorization Code Flow.

OpenIDHybridGrant is a subclass of OpenIDImplicitGrant, so the missing methods are the same, except that OpenIDHybridGrant has one more missing method, that is save_authorization_code. You can implement it like this:

from authlib.oidc.core import grants
from authlib.common.security import generate_token

class OpenIDHybridGrant(grants.OpenIDHybridGrant):
    def save_authorization_code(self, code, request):
        nonce = request.data.get('nonce')
        item = AuthorizationCode(
            code=code,
            client_id=request.client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=request.user.id,
            nonce=nonce,
        )
        db.session.add(item)
        db.session.commit()
        return code

    def exists_nonce(self, nonce, request):
        exists = AuthorizationCode.query.filter_by(
            client_id=request.client_id, nonce=nonce
        ).first()
        return bool(exists)

    def get_jwt_config(self):
        return {
            'key': read_private_key_file(key_path),
            'alg': 'RS512',
            'iss': 'https://example.com',
            'exp': 3600
        }

    def generate_user_info(self, user, scope):
        user_info = UserInfo(sub=user.id, name=user.name)
        if 'email' in scope:
            user_info['email'] = user.email
        return user_info

# register it to grant endpoint
server.register_grant(OpenIDHybridGrant)

Since all OpenID Connect Flow require exists_nonce, get_jwt_config and generate_user_info methods, you can create shared functions for them.

Find the example of OpenID Connect server.

API References of Flask OAuth 2.0 Server

This part of the documentation covers the interface of Flask OAuth 2.0 Server.

class authlib.integrations.flask_oauth2.AuthorizationServer(app=None, query_client=None, save_token=None)

Flask implementation of authlib.oauth2.rfc6749.AuthorizationServer. Initialize it with query_client, save_token methods and Flask app instance:

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

def save_token(token, request):
    if request.user:
        user_id = request.user.id
    else:
        user_id = None
    client = request.client
    tok = Token(
        client_id=client.client_id,
        user_id=user.id,
        **token
    )
    db.session.add(tok)
    db.session.commit()

server = AuthorizationServer(app, query_client, save_token)
# or initialize lazily
server = AuthorizationServer()
server.init_app(app, query_client, save_token)
create_authorization_response(request=None, grant_user=None)

Validate authorization request and create authorization response.

Parameters
  • request – HTTP request instance.

  • grant_user – if granted, it is resource owner. If denied, it is None.

Returns

Response

create_bearer_token_generator(config)

Create a generator function for generating token value. This method will create a Bearer Token generator with authlib.oauth2.rfc6750.BearerToken.

Configurable settings:

  1. OAUTH2_ACCESS_TOKEN_GENERATOR: Boolean or import string, default is True.

  2. OAUTH2_REFRESH_TOKEN_GENERATOR: Boolean or import string, default is False.

  3. OAUTH2_TOKEN_EXPIRES_IN: Dict or import string, default is None.

By default, it will not generate refresh_token, which can be turn on by configure OAUTH2_REFRESH_TOKEN_GENERATOR.

Here are some examples of the token generator:

OAUTH2_ACCESS_TOKEN_GENERATOR = 'your_project.generators.gen_token'

# and in module `your_project.generators`, you can define:

def gen_token(client, grant_type, user, scope):
    # generate token according to these parameters
    token = create_random_token()
    return f'{client.id}-{user.id}-{token}'

Here is an example of OAUTH2_TOKEN_EXPIRES_IN:

OAUTH2_TOKEN_EXPIRES_IN = {
    'authorization_code': 864000,
    'urn:ietf:params:oauth:grant-type:jwt-bearer': 3600,
}
create_endpoint_response(name, request=None)

Validate endpoint request and create endpoint response.

Parameters
  • name – Endpoint name

  • request – HTTP request instance.

Returns

Response

create_token_response(request=None)

Validate token request and create token response.

Parameters

request – HTTP request instance

Validate current HTTP request for authorization page. This page is designed for resource owner to grant or deny the authorization.

register_endpoint(endpoint_cls)

Add extra endpoint to authorization server. e.g. RevocationEndpoint:

authorization_server.register_endpoint(RevocationEndpoint)
Parameters

endpoint_cls – A endpoint class

register_grant(grant_cls, extensions=None)

Register a grant class into the endpoint registry. Developers can implement the grants in authlib.oauth2.rfc6749.grants and register with this method:

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    def authenticate_user(self, credential):
        # ...

authorization_server.register_grant(AuthorizationCodeGrant)
Parameters
  • grant_cls – a grant class.

  • extensions – extensions for the grant class.

class authlib.integrations.flask_oauth2.ResourceProtector

A protecting method for resource servers. Creating a require_oauth decorator easily with ResourceProtector:

from authlib.integrations.flask_oauth2 import ResourceProtector

require_oauth = ResourceProtector()

# add bearer token validator
from authlib.oauth2.rfc6750 import BearerTokenValidator
from project.models import Token

class MyBearerTokenValidator(BearerTokenValidator):
    def authenticate_token(self, token_string):
        return Token.query.filter_by(access_token=token_string).first()

    def request_invalid(self, request):
        return False

    def token_revoked(self, token):
        return False

require_oauth.register_token_validator(MyBearerTokenValidator())

# protect resource with require_oauth

@app.route('/user')
@require_oauth(['profile'])
def user_profile():
    user = User.query.get(current_token.user_id)
    return jsonify(user.to_dict())
raise_error_response(error)

Raise HTTPException for OAuth2Error. Developers can re-implement this method to customize the error response.

Parameters

error – OAuth2Error

Raise

HTTPException

acquire_token(scopes=None)

A method to acquire current valid token with the given scope.

Parameters

scopes – a list of scope values

Returns

token object

acquire(scopes=None)

The with statement of require_oauth. Instead of using a decorator, you can use a with statement instead:

@app.route('/api/user')
def user_api():
    with require_oauth.acquire('profile') as token:
        user = User.query.get(token.user_id)
        return jsonify(user.to_dict())
authlib.integrations.flask_oauth2.current_token

Routes protected by ResourceProtector can access current token with this variable:

from authlib.integrations.flask_oauth2 import current_token

@require_oauth()
@app.route('/user_id')
def user_id():
    # current token instance of the OAuth Token model
    return current_token.user_id
authlib.integrations.flask_oauth2.client_authenticated

Signal when client is authenticated

authlib.integrations.flask_oauth2.token_revoked

Signal when token is revoked

authlib.integrations.flask_oauth2.token_authenticated

Signal when token is authenticated

SQLAlchemy Helper Functions

Warning

We will drop sqla_oauth2 module in version 1.0.

authlib.integrations.sqla_oauth2.create_query_client_func(session, client_model)

Create an query_client function that can be used in authorization server.

Parameters
  • session – SQLAlchemy session

  • client_model – Client model class

authlib.integrations.sqla_oauth2.create_save_token_func(session, token_model)

Create an save_token function that can be used in authorization server.

Parameters
  • session – SQLAlchemy session

  • token_model – Token model class

authlib.integrations.sqla_oauth2.create_query_token_func(session, token_model)

Create an query_token function for revocation, introspection token endpoints.

Parameters
  • session – SQLAlchemy session

  • token_model – Token model class

authlib.integrations.sqla_oauth2.create_revocation_endpoint(session, token_model)

Create a revocation endpoint class with SQLAlchemy session and token model.

Parameters
  • session – SQLAlchemy session

  • token_model – Token model class

authlib.integrations.sqla_oauth2.create_bearer_token_validator(session, token_model)

Create an bearer token validator class with SQLAlchemy session and token model.

Parameters
  • session – SQLAlchemy session

  • token_model – Token model class

Django OAuth Providers

Authlib has built-in Django integrations for building OAuth 1.0 and OAuth 2.0 servers. It is best if developers can read Introduce OAuth 1.0 and Introduce OAuth 2.0 at first.

Django OAuth 1.0 Server

This is just an alpha implementation of Django OAuth 1.0 provider. An OAuth 1 provider contains two servers:

  • Authorization Server: to issue access tokens

  • Resources Server: to serve your users’ resources

At the very beginning, we need to have some basic understanding of the OAuth 1.0.

Important

If you are developing on your localhost, remember to set the environment variable:

export AUTHLIB_INSECURE_TRANSPORT=true

Looking for Django OAuth 1.0 client? Check out Django OAuth Client.

Authorization Server

The Authorization Server provides several endpoints for temporary credentials, authorization, and issuing token credentials. When the resource owner (user) grants the authorization, this server will issue a token credential to the client.

Currently, Authlib Django implementation is using cache a lot, which means you don’t have to handle temporary credentials, timestamp and nonce yourself, they are all built-in.

To create an authorization server, only Client and Token models are required:

from your_project.models import Client, Token
from authlib.integrations.django_oauth1 import CacheAuthorizationServer

authorization_server = CacheAuthorizationServer(Client, Token)
Resource Owner

Resource Owner is the user who is using your service. A resource owner can log in your website with username/email and password, or other methods. In Django, we can use the built in contrib user:

from django.contrib.auth.models import User
Client

A client is an application making protected resource requests on behalf of the resource owner and with its authorization. It contains at least three information:

  • Client Identifier, usually called client_id

  • Client Password, usually called client_secret

  • Client RSA Public Key (if RSA-SHA1 signature method supported)

Authlib has no implementation for client model in Django. You need to implement it yourself:

from django.db import models
from django.contrib.auth.models import User
from authlib.oauth1 import ClientMixin

class Client(models.Model, ClientMixin):
    user = models.ForeignKey(User, on_delete=CASCADE)
    client_id = models.CharField(max_length=48, unique=True, db_index=True)
    client_secret = models.CharField(max_length=48, blank=True)
    default_redirect_uri = models.TextField(blank=False, default='')

    def get_default_redirect_uri(self):
        return self.default_redirect_uri

    def get_client_secret(self):
        return self.client_secret

    def get_rsa_public_key(self):
        return None

A client is registered by a user (developer) on your website. Get a deep inside with ClientMixin API reference.

Token

A token credential is used to access resource owners’ resources. Unlike OAuth 2, the token credential will not expire in OAuth 1. This token credentials are supposed to be saved into a persist database rather than a cache.

Here is an example of how it looks in Django:

from django.db import models
from django.contrib.auth.models import User
from authlib.oauth1 import TokenCredentialMixin

class Token(models.Model, TokenCredentialMixin):
    user = models.ForeignKey(User, on_delete=CASCADE)
    client_id = models.CharField(max_length=48, db_index=True)
    oauth_token = models.CharField(max_length=84, unique=True, db_index=True)
    oauth_token_secret = models.CharField(max_length=84)

    def get_oauth_token(self):
        return self.oauth_token

    def get_oauth_token_secret(self):
        return self.oauth_token_secret
Server Implementation

It is ready to create the endpoints for authorization and issuing tokens. Let’s start with the temporary credentials endpoint, which is used for clients to fetch a temporary credential:

from django.views.decorators.http import require_http_methods

@require_http_methods(["POST"])
def initiate_temporary_credential(request):
    return server.create_temporary_credential_response(request)

The endpoint for resource owner authorization. OAuth 1 Client will redirect user to this authorization page, so that resource owner can grant or deny this request:

from django.shortcuts import render

def authorize(request):
    # make sure that user is logged in for yourself
    if request.method == 'GET':
        try:
            req = server.check_authorization_request(request)
            context = {'req': req}
            return render(request, 'authorize.html', context)
        except OAuth1Error as error:
            context = {'error': error}
            return render(request, 'error.html', context)

    granted = request.POST.get('granted')
    if granted:
        grant_user = request.user
    else:
        grant_user = None

    try:
        return server.create_authorization_response(request, grant_user)
    except OAuth1Error as error:
        context = {'error': error}
        return render(request, 'error.html', context)

Then the final token endpoint. OAuth 1 Client will use the given temporary credential and the oauth_verifier authorized by resource owner to exchange the token credential:

from django.views.decorators.http import require_http_methods

@require_http_methods(["POST"])
def issue_token(request):
    return server.create_token_response(request)

At last, you need to register these views into url patterns.

Protect Resources

Protect users resources, so that only the authorized clients with the authorized access token can access the given scope resources.

A resource server can be a different server other than the authorization server. Here is the way to protect your users’ resources:

from django.http import JsonResponse
from authlib.integrations.django_oauth1 import ResourceProtector
require_oauth = ResourceProtector(Client, TokenCredential)

@require_oauth()
def user_api(request):
    user = request.oauth1_credential.user
    return JsonResponse(dict(username=user.username))

The require_oauth decorator will add a oauth1_credential to request parameter. This oauth1_credential is an instance of the Token model.

Django OAuth 1.0 Server

This part of the documentation covers the interface of Django OAuth 1.0 Server.

class authlib.integrations.django_oauth1.CacheAuthorizationServer(client_model, token_model, token_generator=None)
create_authorization_response(request, grant_user=None)

Validate authorization request and create authorization response. Assume the endpoint for authorization request is https://photos.example.net/authorize, the client redirects Jane’s user-agent to the server’s Resource Owner Authorization endpoint to obtain Jane’s approval for accessing her private photos:

https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola

The server requests Jane to sign in using her username and password and if successful, asks her to approve granting ‘printer.example.com’ access to her private photos. Jane approves the request and her user-agent is redirected to the callback URI provided by the client in the previous request (line breaks are for display purposes only):

http://printer.example.com/ready?
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884
Parameters
  • request – OAuth1Request instance.

  • grant_user – if granted, pass the grant user, otherwise None.

Returns

(status_code, body, headers)

create_authorization_verifier(request)

Create and bind oauth_verifier to temporary credential. It could be re-implemented in this way:

def create_authorization_verifier(self, request):
    verifier = generate_token(36)

    temporary_credential = request.credential
    user_id = request.user.id

    temporary_credential.user_id = user_id
    temporary_credential.oauth_verifier = verifier
    # if the credential has a save method
    temporary_credential.save()

    # remember to return the verifier
    return verifier
Parameters

request – OAuth1Request instance

Returns

A string of oauth_verifier

create_temporary_credential(request)

Generate and save a temporary credential into database or cache. A temporary credential is used for exchanging token credential. This method should be re-implemented:

def create_temporary_credential(self, request):
    oauth_token = generate_token(36)
    oauth_token_secret = generate_token(48)
    temporary_credential = TemporaryCredential(
        oauth_token=oauth_token,
        oauth_token_secret=oauth_token_secret,
        client_id=request.client_id,
        redirect_uri=request.redirect_uri,
    )
    # if the credential has a save method
    temporary_credential.save()
    return temporary_credential
Parameters

request – OAuth1Request instance

Returns

TemporaryCredential instance

create_temporary_credentials_response(request=None)

Validate temporary credentials token request and create response for temporary credentials token. Assume the endpoint of temporary credentials request is https://photos.example.net/initiate:

POST /initiate HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131200",
    oauth_nonce="wIjqoS",
    oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
    oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

The server validates the request and replies with a set of temporary credentials in the body of the HTTP response:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&
oauth_callback_confirmed=true
Parameters

request – OAuth1Request instance.

Returns

(status_code, body, headers)

create_token_credential(request)

Create and save token credential into database. This method would be re-implemented like this:

def create_token_credential(self, request):
    oauth_token = generate_token(36)
    oauth_token_secret = generate_token(48)
    temporary_credential = request.credential

    token_credential = TokenCredential(
        oauth_token=oauth_token,
        oauth_token_secret=oauth_token_secret,
        client_id=temporary_credential.get_client_id(),
        user_id=temporary_credential.get_user_id()
    )
    # if the credential has a save method
    token_credential.save()
    return token_credential
Parameters

request – OAuth1Request instance

Returns

TokenCredential instance

create_token_response(request)

Validate token request and create token response. Assuming the endpoint of token request is https://photos.example.net/token, the callback request informs the client that Jane completed the authorization process. The client then requests a set of token credentials using its temporary credentials (over a secure Transport Layer Security (TLS) channel):

POST /token HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_token="hh5s93j4hdidpola",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131201",
    oauth_nonce="walatlh",
    oauth_verifier="hfdp7dh39dks9884",
    oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

The server validates the request and replies with a set of token credentials in the body of the HTTP response:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00
Parameters

request – OAuth1Request instance.

Returns

(status_code, body, headers)

delete_temporary_credential(request)

Delete temporary credential from database or cache. For instance, if temporary credential is saved in cache:

def delete_temporary_credential(self, request):
    key = 'a-key-prefix:{}'.format(request.token)
    cache.delete(key)
Parameters

request – OAuth1Request instance

exists_nonce(nonce, request)

The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.

Parameters
  • nonce – A string value of oauth_nonce

  • request – OAuth1Request instance

Returns

Boolean

get_client_by_id(client_id)

Get client instance with the given client_id.

Parameters

client_id – A string of client_id

Returns

Client instance

get_temporary_credential(request)

Get the temporary credential from database or cache. A temporary credential should share the same methods as described in models of TemporaryCredentialMixin:

def get_temporary_credential(self, request):
    key = 'a-key-prefix:{}'.format(request.token)
    data = cache.get(key)
    # TemporaryCredential shares methods from TemporaryCredentialMixin
    return TemporaryCredential(data)
Parameters

request – OAuth1Request instance

Returns

TemporaryCredential instance

classmethod register_signature_method(name, verify)

Extend signature method verification.

Parameters
  • name – A string to represent signature method.

  • verify – A function to verify signature.

The verify method accept OAuth1Request as parameter:

def verify_custom_method(request):
    # verify this request, return True or False
    return True

Server.register_signature_method('custom-name', verify_custom_method)
validate_authorization_request(request)

Validate the request for resource owner authorization.

validate_oauth_signature(request)

Validate oauth_signature from HTTP request.

Parameters

request – OAuth1Request instance

validate_temporary_credentials_request(request)

Validate HTTP request for temporary credentials.

validate_timestamp_and_nonce(request)

Validate oauth_timestamp and oauth_nonce in HTTP request.

Parameters

request – OAuth1Request instance

validate_token_request(request)

Validate request for issuing token.

class authlib.integrations.django_oauth1.ResourceProtector(client_model, token_model)
get_client_by_id(client_id)

Get client instance with the given client_id.

Parameters

client_id – A string of client_id

Returns

Client instance

get_token_credential(request)

Fetch the token credential from data store like a database, framework should implement this function.

Parameters

request – OAuth1Request instance

Returns

Token model instance

exists_nonce(nonce, request)

The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.

Parameters
  • nonce – A string value of oauth_nonce

  • request – OAuth1Request instance

Returns

Boolean

Django OAuth 2.0 Server

New in version v0.12.

This section is not a step by step guide on how to create an OAuth 2.0 provider in Django. Instead, we will learn how the Django implementation works, and some technical details in an OAuth 2.0 provider.

At the very beginning, we need to have some basic understanding of the OAuth 2.0.

Important

If you are developing on your localhost, remember to set the environment variable:

export AUTHLIB_INSECURE_TRANSPORT=true

Looking for Django OAuth 2.0 client? Check out Django OAuth Client.

Authorization Server

The Authorization Server provides several endpoints for authorization, issuing tokens, refreshing tokens and revoking tokens. When the resource owner (user) grants the authorization, this server will issue an access token to the client.

Before creating the authorization server, we need to understand several concepts:

Resource Owner

Resource Owner is the user who is using your service. A resource owner can log in your website with username/email and password, or other methods.

In this documentation, we will use the django.contrib.auth.models.User as an example.

Client

Changed in version v1.0: check_token_endpoint_auth_method is deprecated, developers should implement check_endpoint_auth_method instead.

A client is an application making protected resource requests on behalf of the resource owner and with its authorization. It contains at least three information:

  • Client Identifier, usually called client_id

  • Client Password, usually called client_secret

  • Client Token Endpoint Authentication Method

A client is registered by a user (developer) on your website; you MUST implement the missing methods of ClientMixin:

class OAuth2Client(Model, ClientMixin):
    user = ForeignKey(User, on_delete=CASCADE)
    client_id = CharField(max_length=48, unique=True, db_index=True)
    client_secret = CharField(max_length=48, blank=True)
    client_name = CharField(max_length=120)
    redirect_uris = TextField(default='')
    default_redirect_uri = TextField(blank=False, default='')
    scope = TextField(default='')
    response_type = TextField(default='')
    grant_type = TextField(default='')
    token_endpoint_auth_method = CharField(max_length=120, default='')

    # you can add more fields according to your own need
    # check https://tools.ietf.org/html/rfc7591#section-2

    def get_client_id(self):
        return self.client_id

    def get_default_redirect_uri(self):
        return self.default_redirect_uri

    def get_allowed_scope(self, scope):
        if not scope:
            return ''
        allowed = set(scope_to_list(self.scope))
        return list_to_scope([s for s in scope.split() if s in allowed])

    def check_redirect_uri(self, redirect_uri):
        if redirect_uri == self.default_redirect_uri:
            return True
        return redirect_uri in self.redirect_uris

    def has_client_secret(self):
        return bool(self.client_secret)

    def check_client_secret(self, client_secret):
        return self.client_secret == client_secret

    def check_endpoint_auth_method(self, method, endpoint):
        if endpoint == 'token':
          return self.token_endpoint_auth_method == method
        # TODO: developers can update this check method
        return True

    def check_response_type(self, response_type):
        allowed = self.response_type.split()
        return response_type in allowed

    def check_grant_type(self, grant_type):
        allowed = self.grant_type.split()
        return grant_type in allowed
Token

Tokens are used to access the users’ resources. A token is issued with a valid duration, limited scopes and etc. It contains at least:

  • access_token: a token to authorize the http requests.

  • refresh_token: (optional) a token to exchange a new access token

  • client_id: this token is issued to which client

  • expires_at: when will this token expired

  • scope: a limited scope of resources that this token can access

A token is associated with a resource owner; you MUST implement the missing methods of TokenMixin:

import time

def now_timestamp():
    return int(time.time())

class OAuth2Token(Model, TokenMixin):
    user = ForeignKey(User, on_delete=CASCADE)
    client_id = CharField(max_length=48, db_index=True)
    token_type = CharField(max_length=40)
    access_token = CharField(max_length=255, unique=True, null=False)
    refresh_token = CharField(max_length=255, db_index=True)
    scope = TextField(default='')
    revoked = BooleanField(default=False)
    issued_at = IntegerField(null=False, default=now_timestamp)
    expires_in = IntegerField(null=False, default=0)

    def get_client_id(self):
        return self.client_id

    def get_scope(self):
        return self.scope

    def get_expires_in(self):
        return self.expires_in

    def get_expires_at(self):
        return self.issued_at + self.expires_in
Server

Authlib provides a ready to use AuthorizationServer which has built-in tools to handle requests and responses:

from authlib.integrations.django_oauth2 import AuthorizationServer

server = AuthorizationServer(OAuth2Client, OAuth2Token)

The Authorization Server has to provide endpoints:

  1. authorization endpoint if it supports authorization_code or implicit grant types

  2. token endpoint to issue tokens

The AuthorizationServer has provided built-in methods to handle these endpoints:

from django.shortcuts import render
from django.views.decorators.http import require_http_methods

# use ``server.create_authorization_response`` to handle authorization endpoint

def authorize(request):
    if request.method == 'GET':
        grant = server.get_consent_grant(request, end_user=request.user)
        client = grant.client
        scope = client.get_allowed_scope(grant.request.scope)
        context = dict(grant=grant, client=client, scope=scope, user=request.user)
        return render(request, 'authorize.html', context)

    if is_user_confirmed(request):
        # granted by resource owner
        return server.create_authorization_response(request, grant_user=request.user)

    # denied by resource owner
    return server.create_authorization_response(request, grant_user=None)

# use ``server.create_token_response`` to handle token endpoint

@require_http_methods(["POST"])  # we only allow POST for token endpoint
def issue_token(request):
    return server.create_token_response(request)

For now, you have set up the authorization server. But it won’t work since it doesn’t support any grant types yet. Let’s head over to the next chapter.

Register Grants

There are four grant types defined by RFC6749, you can also create your own extended grant. Register the supported grant types to the authorization server.

Authorization Code Grant

Authorization Code Grant is a very common grant type, it is supported by almost every OAuth 2 providers. It uses an authorization code to exchange access token. In this case, we need a place to store the authorization code. It can be kept in a database or a cache like redis. Here is an example of database AuthorizationCode:

from django.db.models import ForeignKey, CASCADE
from django.contrib.auth.models import User
from authlib.oauth2.rfc6749 import AuthorizationCodeMixin

def now_timestamp():
    return int(time.time())

class AuthorizationCode(Model, AuthorizationCodeMixin):
    user = ForeignKey(User, on_delete=CASCADE)
    client_id = CharField(max_length=48, db_index=True)
    code = CharField(max_length=120, unique=True, null=False)
    redirect_uri = TextField(default='', null=True)
    response_type = TextField(default='')
    scope = TextField(default='', null=True)
    auth_time = IntegerField(null=False, default=now_timestamp)

    def is_expired(self):
        return self.auth_time + 300 < time.time()

    def get_redirect_uri(self):
        return self.redirect_uri

    def get_scope(self):
        return self.scope or ''

    def get_auth_time(self):
        return self.auth_time

Note here, you MUST implement the missing methods of AuthorizationCodeMixin API interface.

Later, you can use this AuthorizationCode database model to handle authorization_code grant type. Here is how:

from authlib.oauth2.rfc6749 import grants
from authlib.common.security import generate_token

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    def save_authorization_code(self, code, request):
        client = request.client
        auth_code = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            response_type=request.response_type,
            scope=request.scope,
            user=request.user,
        )
        auth_code.save()
        return auth_code

    def query_authorization_code(self, code, client):
        try:
            item = AuthorizationCode.objects.get(code=code, client_id=client.client_id)
        except AuthorizationCode.DoesNotExist:
            return None

        if not item.is_expired():
            return item

    def delete_authorization_code(self, authorization_code):
        authorization_code.delete()

    def authenticate_user(self, authorization_code):
        return authorization_code.user

# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant)

Note

AuthorizationCodeGrant is the most complex grant.

Default allowed Client Authentication Methods are:

  1. client_secret_basic

  2. client_secret_post

  3. none

You can change it in the subclass, e.g. remove the none authentication method:

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post']

Note

This is important when you want to support OpenID Connect.

Implicit Grant

The implicit grant type is usually used in a browser, when resource owner granted the access, access token is issued in the redirect URI, there is no missing implementation, which means it can be easily registered with:

from authlib.oauth2.rfc6749 import grants

# register it to grant endpoint
server.register_grant(grants.ImplicitGrant)

Implicit Grant is used by public client which has no client_secret. Only allowed Client Authentication Methods: none.

Resource Owner Password Credentials Grant

Resource owner uses their username and password to exchange an access token, this grant type should be used only when the client is trustworthy, implement it with a subclass of ResourceOwnerPasswordCredentialsGrant:

from authlib.oauth2.rfc6749 import grants
from django.contrib.auth.models import User

class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
    def authenticate_user(self, username, password):
        try:
            user = User.objects.get(username=username)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

# register it to grant endpoint
server.register_grant(PasswordGrant)

Default allowed Client Authentication Methods: client_secret_basic. You can add more in the subclass:

class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = [
        'client_secret_basic', 'client_secret_post'
    ]
Client Credentials Grant

Client credentials grant type can access public resources and the client’s creator’s resources. It can be easily registered with:

from authlib.oauth2.rfc6749 import grants

# register it to grant endpoint
server.register_grant(grants.ClientCredentialsGrant)

Default allowed Client Authentication Methods: client_secret_basic. You can add more in the subclass:

class ClientCredentialsGrant(grants.ClientCredentialsGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = [
        'client_secret_basic', 'client_secret_post'
    ]
Refresh Token Grant

Many OAuth 2 providers haven’t implemented refresh token endpoint. Authlib provides it as a grant type, implement it with a subclass of RefreshTokenGrant:

from authlib.oauth2.rfc6749 import grants

class RefreshTokenGrant(grants.RefreshTokenGrant):
    def authenticate_refresh_token(self, refresh_token):
        try:
            item = OAuth2Token.objects.get(refresh_token=refresh_token)
            if item.is_refresh_token_active():
                return item
        except OAuth2Token.DoesNotExist:
            return None

    def authenticate_user(self, credential):
        return credential.user

    def revoke_old_credential(self, credential):
        credential.revoked = True
        credential.save()

# register it to grant endpoint
server.register_grant(RefreshTokenGrant)

Default allowed Client Authentication Methods: client_secret_basic. You can add more in the subclass:

class RefreshTokenGrant(grants.RefreshTokenGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = [
        'client_secret_basic', 'client_secret_post'
    ]

By default, RefreshTokenGrant will not issue a refresh_token in the token response. Developers can change this behavior with:

class RefreshTokenGrant(grants.RefreshTokenGrant):
    INCLUDE_NEW_REFRESH_TOKEN = True
Custom Grant Types

It is also possible to create your own grant types. In Authlib, a Grant supports two endpoints:

  1. Authorization Endpoint: which can handle requests with response_type.

  2. Token Endpoint: which is the endpoint to issue tokens.

Creating a custom grant type with BaseGrant:

from authlib.oauth2.rfc6749.grants import (
    BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin
)

class MyCustomGrant(BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin):
    GRANT_TYPE = 'custom-grant-type-name'

    def validate_authorization_request(self):
        # only needed if using AuthorizationEndpointMixin

    def create_authorization_response(self, grant_user):
        # only needed if using AuthorizationEndpointMixin

    def validate_token_request(self):
        # only needed if using TokenEndpointMixin

    def create_token_response(self):
        # only needed if using TokenEndpointMixin

For a better understanding, you can read the source code of the built-in grant types. And there are extended grant types defined by other specs:

  1. Using JWTs as Authorization Grants

Grant Extensions

Grant can accept extensions. Developers can pass extensions when registering grant:

server.register_grant(AuthorizationCodeGrant, [extension])

For instance, there is CodeChallenge extension in Authlib:

server.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=False)])

Learn more about CodeChallenge at RFC7636: Proof Key for Code Exchange by OAuth Public Clients.

Token Endpoints

Django OAuth 2.0 authorization server has a method to register other token endpoints: authorization_server.register_endpoint. Available endpoints for now:

  1. Revocation Endpoint from RFC7009

  2. Introspection Endpoint from RFC7662

Revocation Endpoint

The revocation endpoint for OAuth authorization servers allows clients to notify the authorization server that a previously obtained refresh or access token is no longer needed.

This allows the authorization server to clean up security credentials. A revocation request will invalidate the actual token and, if applicable, other tokens based on the same authorization grant.

For example, a client may request the revocation of a refresh token with the following request:

POST /oauth/revoke HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token

In Authlib Django OAuth 2.0 provider, we can simply add this feature:

from authlib.integrations.django_oauth2 import RevocationEndpoint
from django.views.decorators.http import require_http_methods

# see Authorization Server chapter
server.register_endpoint(RevocationEndpoint)

@require_http_methods(["POST"])
def revoke_token(request):
    return server.create_endpoint_response(RevocationEndpoint.ENDPOINT_NAME, request)

That’s all we need. Add this revoke_token to your routes to enable it. The suggested url path is /oauth/revoke.

Introspection Endpoint

Check Register Introspection Endpoint to get more details.

Resource Server

Protect users resources, so that only the authorized clients with the authorized access token can access the given scope resources.

A resource server can be a different server other than the authorization server. Here is the way to protect your users’ resources in Django:

from authlib.integrations.django_oauth2 import ResourceProtector, BearerTokenValidator
from django.http import JsonResponse

require_oauth = ResourceProtector()
require_oauth.register_token_validator(BearerTokenValidator(OAuth2Token))

@require_oauth('profile')
def user_profile(request):
    user = request.oauth_token.user
    return JsonResponse(dict(sub=user.pk, username=user.username))

If the resource is not protected by a scope, use None:

@require_oauth()
def user_profile(request):
    user = request.oauth_token.user
    return JsonResponse(dict(sub=user.pk, username=user.username))

# or with None

@require_oauth(None)
def user_profile(request):
    user = request.oauth_token.user
    return JsonResponse(dict(sub=user.pk, username=user.username))

The decorator require_oauth will add an oauth_token property on request, which is the instance of current in-use Token.

Multiple Scopes

Changed in version v1.0.

You can apply multiple scopes to one endpoint in AND, OR and mix modes. Here are some examples:

@require_oauth(['profile email'])
def user_profile(request):
    user = request.oauth_token.user
    return JsonResponse(dict(sub=user.pk, username=user.username))

It requires the token containing both profile and email scope.

@require_oauth(['profile', 'email'])
def user_profile(request):
    user = request.oauth_token.user
    return JsonResponse(dict(sub=user.pk, username=user.username))

It requires the token containing either profile or email scope.

It is also possible to mix AND and OR logic. e.g.:

@app.route('/profile')
@require_oauth(['profile email', 'user'])
def user_profile(request):
    user = request.oauth_token.user
    return JsonResponse(dict(sub=user.pk, username=user.username))

This means if the token will be valid if:

  1. token contains both profile and email scope

  2. or token contains user scope

Optional require_oauth

There is one more parameter for require_oauth which is used to serve public endpoints:

@require_oauth(optional=True)
def timeline_api(request):
    if request.oauth_token:
        return get_user_timeline(request.oauth_token.user)
    return get_public_timeline(request)
Django OIDC Provider

OpenID Connect 1.0 are built custom grant types and grant extensions. You need to read the Authorization Server chapter at first.

Looking for OpenID Connect Client? Head over to Django OAuth Client.

Understand JWT

OpenID Connect 1.0 uses JWT a lot. Make sure you have the basic understanding of JOSE Guide.

For OpenID Connect, we need to understand at lease four concepts:

  1. alg: Algorithm for JWT

  2. key: Private key for JWT

  3. iss: Issuer value for JWT

  4. exp: JWT expires time

alg

The algorithm to sign a JWT. This is the alg value defined in header part of a JWS:

{"alg": "RS256"}

The available algorithms are defined in RFC7518: JSON Web Algorithms, which are:

  • HS256: HMAC using SHA-256

  • HS384: HMAC using SHA-384

  • HS512: HMAC using SHA-512

  • RS256: RSASSA-PKCS1-v1_5 using SHA-256

  • RS384: RSASSA-PKCS1-v1_5 using SHA-384

  • RS512: RSASSA-PKCS1-v1_5 using SHA-512

  • ES256: ECDSA using P-256 and SHA-256

  • ES384: ECDSA using P-384 and SHA-384

  • ES512: ECDSA using P-521 and SHA-512

  • PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256

  • PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384

  • PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512

The HMAC using SHA algorithms are not suggested since you need to share secrets between server and client. Most OpenID Connect services are using RS256.

key

A private key is required to generate JWT. The key that you are going to use dependents on the alg you are using. For instance, the alg is RS256, you need to use an RSA private key. It can be set with:

key = '''-----BEGIN RSA PRIVATE KEY-----\nMIIEog...'''

# or in JWK format
key = {"kty": "RSA", "n": ...}
iss

The iss value in JWT payload. The value can be your website name or URL. For example, Google is using:

{"iss": "https://accounts.google.com"}
Code Flow

OpenID Connect authorization code flow relies on the OAuth2 authorization code flow and extends it. In OpenID Connect, there will be a nonce parameter in request, we need to save it into database for later use. In this case, we have to rewrite our AuthorizationCode db model:

class AuthorizationCode(Model, AuthorizationCodeMixin):
    user = ForeignKey(User, on_delete=CASCADE)
    client_id = CharField(max_length=48, db_index=True)
    code = CharField(max_length=120, unique=True, null=False)
    redirect_uri = TextField(default='', null=True)
    response_type = TextField(default='')
    scope = TextField(default='', null=True)
    auth_time = IntegerField(null=False, default=now_timestamp)

    # add nonce
    nonce = CharField(max_length=120, default='', null=True)

    # ... other fields and methods ...

OpenID Connect Code flow is the same as Authorization Code flow, but with extended features. We can apply the OpenIDCode extension to AuthorizationCodeGrant.

First, we need to implement the missing methods for OpenIDCode:

from authlib.oidc.core import grants, UserInfo

class OpenIDCode(grants.OpenIDCode):
    def exists_nonce(self, nonce, request):
        try:
            AuthorizationCode.objects.get(
                client_id=request.client_id, nonce=nonce
            )
            return True
        except AuthorizationCode.DoesNotExist:
            return False

    def get_jwt_config(self, grant):
        return {
            'key': read_private_key_file(key_path),
            'alg': 'RS512',
            'iss': 'https://example.com',
            'exp': 3600
        }

    def generate_user_info(self, user, scope):
        user_info = UserInfo(sub=str(user.pk), name=user.name)
        if 'email' in scope:
            user_info['email'] = user.email
        return user_info

Second, since there is one more nonce value in AuthorizationCode data, we need to save this value into database. In this case, we have to update our AuthorizationCodeGrant.save_authorization_code method:

class AuthorizationCodeGrant(_AuthorizationCodeGrant):
    def save_authorization_code(self, code, request):
        # openid request MAY have "nonce" parameter
        nonce = request.data.get('nonce')
        client = request.client
        auth_code = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user=request.user,
            nonce=nonce,
        )
        auth_code.save()
        return auth_code

Finally, you can register AuthorizationCodeGrant with OpenIDCode extension:

# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant, [OpenIDCode(require_nonce=True)])

The difference between OpenID Code flow and the standard code flow is that OpenID Connect request has a scope of “openid”:

GET /authorize?
response_type=code
&scope=openid%20profile%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1
Host: server.example.com

With the example above, you will also have to change the scope of your client in your application to something like openid profile email.

Now that you added the openid scope to your application, an OpenID token will be provided to this app whenever a client asks for a token with an openid scope.

Implicit Flow

The Implicit Flow is mainly used by Clients implemented in a browser using a scripting language. You need to implement the missing methods of OpenIDImplicitGrant before register it:

from authlib.oidc.core import grants

class OpenIDImplicitGrant(grants.OpenIDImplicitGrant):
    def exists_nonce(self, nonce, request):
        try:
            AuthorizationCode.objects.get(
                client_id=request.client_id, nonce=nonce)
            )
            return True
        except AuthorizationCode.DoesNotExist:
            return False

    def get_jwt_config(self):
        return {
            'key': read_private_key_file(key_path),
            'alg': 'RS512',
            'iss': 'https://example.com',
            'exp': 3600
        }

    def generate_user_info(self, user, scope):
        user_info = UserInfo(sub=user.id, name=user.name)
        if 'email' in scope:
            user_info['email'] = user.email
        return user_info

server.register_grant(OpenIDImplicitGrant)
Hybrid Flow

Hybrid flow is a mix of the code flow and implicit flow. You only need to implement the authorization endpoint part, token endpoint will be handled by Authorization Code Flow.

OpenIDHybridGrant is a subclass of OpenIDImplicitGrant, so the missing methods are the same, except that OpenIDHybridGrant has one more missing method, that is save_authorization_code. You can implement it like this:

from authlib.oidc.core import grants

class OpenIDHybridGrant(grants.OpenIDHybridGrant):
    def save_authorization_code(self, code, request):
        # openid request MAY have "nonce" parameter
        nonce = request.data.get('nonce')
        client = request.client
        auth_code = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user=request.user,
            nonce=nonce,
        )
        auth_code.save()
        return auth_code

    def exists_nonce(self, nonce, request):
        try:
            AuthorizationCode.objects.get(
                client_id=request.client_id, nonce=nonce)
            )
            return True
        except AuthorizationCode.DoesNotExist:
            return False

    def get_jwt_config(self):
        return {
            'key': read_private_key_file(key_path),
            'alg': 'RS512',
            'iss': 'https://example.com',
            'exp': 3600
        }

    def generate_user_info(self, user, scope):
        user_info = UserInfo(sub=user.id, name=user.name)
        if 'email' in scope:
            user_info['email'] = user.email
        return user_info

# register it to grant endpoint
server.register_grant(OpenIDHybridGrant)

Since all OpenID Connect Flow requires exists_nonce, get_jwt_config and generate_user_info methods, you can create shared functions for them.

API References of Django OAuth 2.0 Server

This part of the documentation covers the interface of Django OAuth 2.0 Server.

class authlib.integrations.django_oauth2.AuthorizationServer(client_model, token_model)

Django implementation of authlib.oauth2.rfc6749.AuthorizationServer. Initialize it with client model and token model:

from authlib.integrations.django_oauth2 import AuthorizationServer
from your_project.models import OAuth2Client, OAuth2Token

server = AuthorizationServer(OAuth2Client, OAuth2Token)
create_authorization_response(request=None, grant_user=None)

Validate authorization request and create authorization response.

Parameters
  • request – HTTP request instance.

  • grant_user – if granted, it is resource owner. If denied, it is None.

Returns

Response

create_endpoint_response(name, request=None)

Validate endpoint request and create endpoint response.

Parameters
  • name – Endpoint name

  • request – HTTP request instance.

Returns

Response

create_token_response(request=None)

Validate token request and create token response.

Parameters

request – HTTP request instance

Validate current HTTP request for authorization page. This page is designed for resource owner to grant or deny the authorization.

register_endpoint(endpoint_cls)

Add extra endpoint to authorization server. e.g. RevocationEndpoint:

authorization_server.register_endpoint(RevocationEndpoint)
Parameters

endpoint_cls – A endpoint class

register_grant(grant_cls, extensions=None)

Register a grant class into the endpoint registry. Developers can implement the grants in authlib.oauth2.rfc6749.grants and register with this method:

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    def authenticate_user(self, credential):
        # ...

authorization_server.register_grant(AuthorizationCodeGrant)
Parameters
  • grant_cls – a grant class.

  • extensions – extensions for the grant class.

class authlib.integrations.django_oauth2.ResourceProtector
acquire_token(request, scopes=None)

A method to acquire current valid token with the given scope.

Parameters
  • request – Django HTTP request instance

  • scopes – a list of scope values

Returns

token object

class authlib.integrations.django_oauth2.BearerTokenValidator(token_model, realm=None, **extra_attributes)
authenticate_token(token_string)

A method to query token from database with the given token string. Developers MUST re-implement this method. For instance:

def authenticate_token(self, token_string):
    return get_token_from_database(token_string)
Parameters

token_string – A string to represent the access_token.

Returns

token

class authlib.integrations.django_oauth2.RevocationEndpoint(server)

The revocation endpoint for OAuth authorization servers allows clients to notify the authorization server that a previously obtained refresh or access token is no longer needed.

Register it into authorization server, and create token endpoint response for token revocation:

from django.views.decorators.http import require_http_methods

# see register into authorization server instance
server.register_endpoint(RevocationEndpoint)

@require_http_methods(["POST"])
def revoke_token(request):
    return server.create_endpoint_response(
        RevocationEndpoint.ENDPOINT_NAME,
        request
    )
query_token(token, token_type_hint)

Query requested token from database.

revoke_token(token, request)

Mark the give token as revoked.

authlib.integrations.django_oauth2.client_authenticated

Signal when client is authenticated

authlib.integrations.django_oauth2.token_revoked

Signal when token is revoked

authlib.integrations.django_oauth2.token_authenticated

Signal when token is authenticated

Specifications

Guide on specifications. You don’t have to read this section if you are just using Authlib. But it would be good for you to understand how Authlib works.

RFC5849: The OAuth 1.0 Protocol

This section contains the generic implementation of RFC5849. Learn how to create an OAuth 1.0 provider in these frameworks:

  1. Flask: Flask OAuth 1.0 Server.

  2. Django: Django OAuth 1.0 Server.

Servers
class authlib.oauth1.rfc5849.AuthorizationServer
create_authorization_response(request, grant_user=None)

Validate authorization request and create authorization response. Assume the endpoint for authorization request is https://photos.example.net/authorize, the client redirects Jane’s user-agent to the server’s Resource Owner Authorization endpoint to obtain Jane’s approval for accessing her private photos:

https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola

The server requests Jane to sign in using her username and password and if successful, asks her to approve granting ‘printer.example.com’ access to her private photos. Jane approves the request and her user-agent is redirected to the callback URI provided by the client in the previous request (line breaks are for display purposes only):

http://printer.example.com/ready?
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884
Parameters
  • request – OAuth1Request instance.

  • grant_user – if granted, pass the grant user, otherwise None.

Returns

(status_code, body, headers)

create_authorization_verifier(request)

Create and bind oauth_verifier to temporary credential. It could be re-implemented in this way:

def create_authorization_verifier(self, request):
    verifier = generate_token(36)

    temporary_credential = request.credential
    user_id = request.user.id

    temporary_credential.user_id = user_id
    temporary_credential.oauth_verifier = verifier
    # if the credential has a save method
    temporary_credential.save()

    # remember to return the verifier
    return verifier
Parameters

request – OAuth1Request instance

Returns

A string of oauth_verifier

create_temporary_credential(request)

Generate and save a temporary credential into database or cache. A temporary credential is used for exchanging token credential. This method should be re-implemented:

def create_temporary_credential(self, request):
    oauth_token = generate_token(36)
    oauth_token_secret = generate_token(48)
    temporary_credential = TemporaryCredential(
        oauth_token=oauth_token,
        oauth_token_secret=oauth_token_secret,
        client_id=request.client_id,
        redirect_uri=request.redirect_uri,
    )
    # if the credential has a save method
    temporary_credential.save()
    return temporary_credential
Parameters

request – OAuth1Request instance

Returns

TemporaryCredential instance

create_temporary_credentials_response(request=None)

Validate temporary credentials token request and create response for temporary credentials token. Assume the endpoint of temporary credentials request is https://photos.example.net/initiate:

POST /initiate HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131200",
    oauth_nonce="wIjqoS",
    oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
    oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

The server validates the request and replies with a set of temporary credentials in the body of the HTTP response:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&
oauth_callback_confirmed=true
Parameters

request – OAuth1Request instance.

Returns

(status_code, body, headers)

create_token_credential(request)

Create and save token credential into database. This method would be re-implemented like this:

def create_token_credential(self, request):
    oauth_token = generate_token(36)
    oauth_token_secret = generate_token(48)
    temporary_credential = request.credential

    token_credential = TokenCredential(
        oauth_token=oauth_token,
        oauth_token_secret=oauth_token_secret,
        client_id=temporary_credential.get_client_id(),
        user_id=temporary_credential.get_user_id()
    )
    # if the credential has a save method
    token_credential.save()
    return token_credential
Parameters

request – OAuth1Request instance

Returns

TokenCredential instance

create_token_response(request)

Validate token request and create token response. Assuming the endpoint of token request is https://photos.example.net/token, the callback request informs the client that Jane completed the authorization process. The client then requests a set of token credentials using its temporary credentials (over a secure Transport Layer Security (TLS) channel):

POST /token HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_token="hh5s93j4hdidpola",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131201",
    oauth_nonce="walatlh",
    oauth_verifier="hfdp7dh39dks9884",
    oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

The server validates the request and replies with a set of token credentials in the body of the HTTP response:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00
Parameters

request – OAuth1Request instance.

Returns

(status_code, body, headers)

delete_temporary_credential(request)

Delete temporary credential from database or cache. For instance, if temporary credential is saved in cache:

def delete_temporary_credential(self, request):
    key = 'a-key-prefix:{}'.format(request.token)
    cache.delete(key)
Parameters

request – OAuth1Request instance

exists_nonce(nonce, request)

The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.

Parameters
  • nonce – A string value of oauth_nonce

  • request – OAuth1Request instance

Returns

Boolean

get_client_by_id(client_id)

Get client instance with the given client_id.

Parameters

client_id – A string of client_id

Returns

Client instance

get_temporary_credential(request)

Get the temporary credential from database or cache. A temporary credential should share the same methods as described in models of TemporaryCredentialMixin:

def get_temporary_credential(self, request):
    key = 'a-key-prefix:{}'.format(request.token)
    data = cache.get(key)
    # TemporaryCredential shares methods from TemporaryCredentialMixin
    return TemporaryCredential(data)
Parameters

request – OAuth1Request instance

Returns

TemporaryCredential instance

classmethod register_signature_method(name, verify)

Extend signature method verification.

Parameters
  • name – A string to represent signature method.

  • verify – A function to verify signature.

The verify method accept OAuth1Request as parameter:

def verify_custom_method(request):
    # verify this request, return True or False
    return True

Server.register_signature_method('custom-name', verify_custom_method)
validate_authorization_request(request)

Validate the request for resource owner authorization.

validate_oauth_signature(request)

Validate oauth_signature from HTTP request.

Parameters

request – OAuth1Request instance

validate_temporary_credentials_request(request)

Validate HTTP request for temporary credentials.

validate_timestamp_and_nonce(request)

Validate oauth_timestamp and oauth_nonce in HTTP request.

Parameters

request – OAuth1Request instance

validate_token_request(request)

Validate request for issuing token.

class authlib.oauth1.rfc5849.ResourceProtector
exists_nonce(nonce, request)

The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.

Parameters
  • nonce – A string value of oauth_nonce

  • request – OAuth1Request instance

Returns

Boolean

get_client_by_id(client_id)

Get client instance with the given client_id.

Parameters

client_id – A string of client_id

Returns

Client instance

get_token_credential(request)

Fetch the token credential from data store like a database, framework should implement this function.

Parameters

request – OAuth1Request instance

Returns

Token model instance

classmethod register_signature_method(name, verify)

Extend signature method verification.

Parameters
  • name – A string to represent signature method.

  • verify – A function to verify signature.

The verify method accept OAuth1Request as parameter:

def verify_custom_method(request):
    # verify this request, return True or False
    return True

Server.register_signature_method('custom-name', verify_custom_method)
validate_oauth_signature(request)

Validate oauth_signature from HTTP request.

Parameters

request – OAuth1Request instance

validate_timestamp_and_nonce(request)

Validate oauth_timestamp and oauth_nonce in HTTP request.

Parameters

request – OAuth1Request instance

Models Mixin
class authlib.oauth1.rfc5849.ClientMixin
get_client_secret()

A method to return the client_secret of this client. For instance, the database table has a column called client_secret:

def get_client_secret(self):
    return self.client_secret
get_default_redirect_uri()

A method to get client default redirect_uri. For instance, the database table for client has a column called default_redirect_uri:

def get_default_redirect_uri(self):
    return self.default_redirect_uri
Returns

A URL string

get_rsa_public_key()

A method to get the RSA public key for RSA-SHA1 signature method. For instance, the value is saved on column rsa_public_key:

def get_rsa_public_key(self):
    return self.rsa_public_key
class authlib.oauth1.rfc5849.TemporaryCredentialMixin
check_verifier(verifier)

A method to check if the given verifier matches this temporary credential. For instance that this temporary credential has recorded the value in database as column oauth_verifier:

def check_verifier(self, verifier):
    return self.oauth_verifier == verifier
Returns

Boolean

get_client_id()

A method to get the client_id associated with this credential. For instance, the table in the database has a column client_id:

def get_client_id(self):
    return self.client_id
get_oauth_token()

A method to get the value of oauth_token. For instance, the database table has a column called oauth_token:

def get_oauth_token(self):
    return self.oauth_token
Returns

A string

get_oauth_token_secret()

A method to get the value of oauth_token_secret. For instance, the database table has a column called oauth_token_secret:

def get_oauth_token_secret(self):
    return self.oauth_token_secret
Returns

A string

get_redirect_uri()

A method to get temporary credential’s oauth_callback. For instance, the database table for temporary credential has a column called oauth_callback:

def get_redirect_uri(self):
    return self.oauth_callback
Returns

A URL string

class authlib.oauth1.rfc5849.TokenCredentialMixin
get_oauth_token()

A method to get the value of oauth_token. For instance, the database table has a column called oauth_token:

def get_oauth_token(self):
    return self.oauth_token
Returns

A string

get_oauth_token_secret()

A method to get the value of oauth_token_secret. For instance, the database table has a column called oauth_token_secret:

def get_oauth_token_secret(self):
    return self.oauth_token_secret
Returns

A string

RFC6749: The OAuth 2.0 Authorization Framework

This section contains the generic implementation of RFC6749. You should read Introduce OAuth 2.0 at first. Here are some tips:

  1. Have a better understanding of OAuth 2.0

  2. How to use OAuth 2 Session for Requests

  3. How to implement Flask OAuth Client

  4. How to implement Flask OAuth 2.0 Server

  5. How to implement Django OAuth Client

  6. How to implement Django OAuth 2.0 Server

API References

Here are the API references for developers. For framework level interfaces, check:

Servers
class authlib.oauth2.rfc6749.AuthorizationServer(scopes_supported=None)

Authorization server that handles Authorization Endpoint and Token Endpoint.

Parameters

scopes_supported – A list of supported scopes by this authorization server.

authenticate_client(request, methods, endpoint='token')

Authenticate client via HTTP request information with the given methods, such as client_secret_basic, client_secret_post.

create_authorization_response(request=None, grant_user=None)

Validate authorization request and create authorization response.

Parameters
  • request – HTTP request instance.

  • grant_user – if granted, it is resource owner. If denied, it is None.

Returns

Response

create_endpoint_response(name, request=None)

Validate endpoint request and create endpoint response.

Parameters
  • name – Endpoint name

  • request – HTTP request instance.

Returns

Response

create_json_request(request)

This method MUST be implemented in framework integrations. It is used to create an HttpRequest instance.

Parameters

request – the “request” instance in framework

Returns

HttpRequest instance

create_oauth2_request(request)

This method MUST be implemented in framework integrations. It is used to create an OAuth2Request instance.

Parameters

request – the “request” instance in framework

Returns

OAuth2Request instance

create_token_response(request=None)

Validate token request and create token response.

Parameters

request – HTTP request instance

generate_token(grant_type, client, user=None, scope=None, expires_in=None, include_refresh_token=True)

Generate the token dict.

Parameters
  • grant_type – current requested grant_type.

  • client – the client that making the request.

  • user – current authorized user.

  • expires_in – if provided, use this value as expires_in.

  • scope – current requested scope.

  • include_refresh_token – should refresh_token be included.

Returns

Token dict

get_authorization_grant(request)

Find the authorization grant for current request.

Parameters

request – OAuth2Request instance.

Returns

grant instance

Validate current HTTP request for authorization page. This page is designed for resource owner to grant or deny the authorization.

get_error_uri(request, error)

Return a URI for the given error, framework may implement this method.

get_token_grant(request)

Find the token grant for current request.

Parameters

request – OAuth2Request instance.

Returns

grant instance

handle_response(status, body, headers)

Return HTTP response. Framework MUST implement this function.

query_client(client_id)

Query OAuth client by client_id. The client model class MUST implement the methods described by ClientMixin.

register_client_auth_method(method, func)

Add more client auth method. The default methods are:

  • none: The client is a public client and does not have a client secret

  • client_secret_post: The client uses the HTTP POST parameters

  • client_secret_basic: The client uses HTTP Basic

Parameters
  • method – Name of the Auth method

  • func – Function to authenticate the client

The auth method accept two parameters: query_client and request, an example for this method:

def authenticate_client_via_custom(query_client, request):
    client_id = request.headers['X-Client-Id']
    client = query_client(client_id)
    do_some_validation(client)
    return client

authorization_server.register_client_auth_method(
    'custom', authenticate_client_via_custom)
register_endpoint(endpoint_cls)

Add extra endpoint to authorization server. e.g. RevocationEndpoint:

authorization_server.register_endpoint(RevocationEndpoint)
Parameters

endpoint_cls – A endpoint class

register_grant(grant_cls, extensions=None)

Register a grant class into the endpoint registry. Developers can implement the grants in authlib.oauth2.rfc6749.grants and register with this method:

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    def authenticate_user(self, credential):
        # ...

authorization_server.register_grant(AuthorizationCodeGrant)
Parameters
  • grant_cls – a grant class.

  • extensions – extensions for the grant class.

register_token_generator(grant_type, func)

Register a function as token generator for the given grant_type. Developers MUST register a default token generator with a special grant_type=default:

def generate_bearer_token(grant_type, client, user=None, scope=None,
                          expires_in=None, include_refresh_token=True):
    token = {'token_type': 'Bearer', 'access_token': ...}
    if include_refresh_token:
        token['refresh_token'] = ...
    ...
    return token

authorization_server.register_token_generator('default', generate_bearer_token)

If you register a generator for a certain grant type, that generator will only works for the given grant type:

authorization_server.register_token_generator('client_credentials', generate_bearer_token)
Parameters
  • grant_type – string name of the grant type

  • func – a function to generate token

save_token(token, request)

Define function to save the generated token into database.

send_signal(name, *args, **kwargs)

Framework integration can re-implement this method to support signal system.

validate_requested_scope(scope, state=None)

Validate if requested scope is supported by Authorization Server. Developers CAN re-write this method to meet your needs.

class authlib.oauth2.rfc6749.ResourceProtector
get_token_validator(token_type)

Get token validator from registry for the given token type.

parse_request_authorization(request)

Parse the token and token validator from request Authorization header. Here is an example of Authorization header:

Authorization: Bearer a-token-string

This method will parse this header, if it can find the validator for Bearer, it will return the validator and a-token-string.

Returns

validator, token_string

Raise

MissingAuthorizationError

Raise

UnsupportedTokenTypeError

register_token_validator(validator: authlib.oauth2.rfc6749.resource_protector.TokenValidator)

Register a token validator for a given Authorization type. Authlib has a built-in BearerTokenValidator per rfc6750.

validate_request(scopes, request)

Validate the request and return a token.

Client Model
class authlib.oauth2.rfc6749.ClientMixin

Implementation of OAuth 2 Client described in Section 2 with some methods to help validation. A client has at least these information:

  • client_id: A string represents client identifier.

  • client_secret: A string represents client password.

  • token_endpoint_auth_method: A way to authenticate client at token

    endpoint.

check_client_secret(client_secret)

Check client_secret matching with the client. For instance, in the client table, the column is called client_secret:

import secrets

def check_client_secret(self, client_secret):
    return secrets.compare_digest(self.client_secret, client_secret)
Parameters

client_secret – A string of client secret

Returns

bool

check_endpoint_auth_method(method, endpoint)

Check if client support the given method for the given endpoint. There is a token_endpoint_auth_method defined via RFC7591. Developers MAY re-implement this method with:

def check_endpoint_auth_method(self, method, endpoint):
    if endpoint == 'token':
        # if client table has ``token_endpoint_auth_method``
        return self.token_endpoint_auth_method == method
    return True

Method values defined by this specification are:

  • “none”: The client is a public client as defined in OAuth 2.0,

    and does not have a client secret.

  • “client_secret_post”: The client uses the HTTP POST parameters

    as defined in OAuth 2.0

  • “client_secret_basic”: The client uses HTTP Basic as defined in

    OAuth 2.0

check_grant_type(grant_type)

Validate if the client can handle the given grant_type. There are four grant types defined by RFC6749:

  • authorization_code

  • implicit

  • client_credentials

  • password

For instance, there is a allowed_grant_types column in your client:

def check_grant_type(self, grant_type):
    return grant_type in self.grant_types
Parameters

grant_type – the requested grant_type string.

Returns

bool

check_redirect_uri(redirect_uri)

Validate redirect_uri parameter in Authorization Endpoints. For instance, in the client table, there is an allowed_redirect_uris column:

def check_redirect_uri(self, redirect_uri):
    return redirect_uri in self.allowed_redirect_uris
Parameters

redirect_uri – A URL string for redirecting.

Returns

bool

check_response_type(response_type)

Validate if the client can handle the given response_type. There are two response types defined by RFC6749: code and token. For instance, there is a allowed_response_types column in your client:

def check_response_type(self, response_type):
    return response_type in self.response_types
Parameters

response_type – the requested response_type string.

Returns

bool

get_allowed_scope(scope)

A method to return a list of requested scopes which are supported by this client. For instance, there is a scope column:

def get_allowed_scope(self, scope):
    if not scope:
        return ''
    allowed = set(scope_to_list(self.scope))
    return list_to_scope([s for s in scope.split() if s in allowed])
Parameters

scope – the requested scope.

Returns

string of scope

get_client_id()

A method to return client_id of the client. For instance, the value in database is saved in a column called client_id:

def get_client_id(self):
    return self.client_id
Returns

string

get_default_redirect_uri()

A method to get client default redirect_uri. For instance, the database table for client has a column called default_redirect_uri:

def get_default_redirect_uri(self):
    return self.default_redirect_uri
Returns

A URL string

Token Model
class authlib.oauth2.rfc6749.AuthorizationCodeMixin
get_redirect_uri()

A method to get authorization code’s redirect_uri. For instance, the database table for authorization code has a column called redirect_uri:

def get_redirect_uri(self):
    return self.redirect_uri
Returns

A URL string

get_scope()

A method to get scope of the authorization code. For instance, the column is called scope:

def get_scope(self):
    return self.scope
Returns

scope string

class authlib.oauth2.rfc6749.TokenMixin
check_client(client)

A method to check if this token is issued to the given client. For instance, client_id is saved on token table:

def check_client(self, client):
    return self.client_id == client.client_id
Returns

bool

get_expires_in()

A method to get the expires_in value of the token. e.g. the column is called expires_in:

def get_expires_in(self):
    return self.expires_in
Returns

timestamp int

get_scope()

A method to get scope of the authorization code. For instance, the column is called scope:

def get_scope(self):
    return self.scope
Returns

scope string

is_expired()

A method to define if this token is expired. For instance, there is a column expired_at in the table:

def is_expired(self):
    return self.expired_at < now
Returns

boolean

is_revoked()

A method to define if this token is revoked. For instance, there is a boolean column revoked in the table:

def is_revoked(self):
    return self.revoked
Returns

boolean

Errors
class authlib.oauth2.rfc6749.OAuth2Error(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)
get_body()

Get a list of body.

class authlib.oauth2.rfc6749.InsecureTransportError(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)
classmethod check(uri)

Check and raise InsecureTransportError with the given URI.

class authlib.oauth2.rfc6749.InvalidRequestError(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)

The request is missing a required parameter, includes an unsupported parameter value (other than grant type), repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed.

https://tools.ietf.org/html/rfc6749#section-5.2

class authlib.oauth2.rfc6749.InvalidClientError(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)

Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported. If the client attempted to authenticate via the “Authorization” request header field, the authorization server MUST respond with an HTTP 401 (Unauthorized) status code and include the “WWW-Authenticate” response header field matching the authentication scheme used by the client.

https://tools.ietf.org/html/rfc6749#section-5.2

class authlib.oauth2.rfc6749.InvalidGrantError(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)

The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.

https://tools.ietf.org/html/rfc6749#section-5.2

class authlib.oauth2.rfc6749.UnauthorizedClientError(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)

The authenticated client is not authorized to use this authorization grant type.

https://tools.ietf.org/html/rfc6749#section-5.2

class authlib.oauth2.rfc6749.UnsupportedGrantTypeError(grant_type)

The authorization grant type is not supported by the authorization server.

https://tools.ietf.org/html/rfc6749#section-5.2

class authlib.oauth2.rfc6749.InvalidScopeError(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)

The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner.

https://tools.ietf.org/html/rfc6749#section-5.2

class authlib.oauth2.rfc6749.AccessDeniedError(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)

The resource owner or authorization server denied the request.

Used in authorization endpoint for “code” and “implicit”. Defined in Section 4.1.2.1.

Grant Types
class authlib.oauth2.rfc6749.grants.AuthorizationCodeGrant(request, server)

The authorization code grant type is used to obtain both access tokens and refresh tokens and is optimized for confidential clients. Since this is a redirection-based flow, the client must be capable of interacting with the resource owner’s user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server:

+----------+
| Resource |
|   Owner  |
|          |
+----------+
     ^
     |
    (B)
+----|-----+          Client Identifier      +---------------+
|         -+----(A)-- & Redirection URI ---->|               |
|  User-   |                                 | Authorization |
|  Agent  -+----(B)-- User authenticates --->|     Server    |
|          |                                 |               |
|         -+----(C)-- Authorization Code ---<|               |
+-|----|---+                                 +---------------+
  |    |                                         ^      v
 (A)  (C)                                        |      |
  |    |                                         |      |
  ^    v                                         |      |
+---------+                                      |      |
|         |>---(D)-- Authorization Code ---------'      |
|  Client |          & Redirection URI                  |
|         |                                             |
|         |<---(E)----- Access Token -------------------'
+---------+       (w/ Optional Refresh Token)
TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post']

Allowed client auth methods for token endpoint

AUTHORIZATION_CODE_LENGTH = 48

Generated “code” length

validate_authorization_request()

The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the “application/x-www-form-urlencoded” format. Per Section 4.1.1.

response_type

REQUIRED. Value MUST be set to “code”.

client_id

REQUIRED. The client identifier as described in Section 2.2.

redirect_uri

OPTIONAL. As described in Section 3.1.2.

scope

OPTIONAL. The scope of the access request as described by Section 3.3.

state

RECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in Section 10.12.

The client directs the resource owner to the constructed URI using an HTTP redirection response, or by other means available to it via the user-agent.

For example, the client directs the user-agent to make the following HTTP request using TLS (with extra line breaks for display purposes only):

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

The authorization server validates the request to ensure that all required parameters are present and valid. If the request is valid, the authorization server authenticates the resource owner and obtains an authorization decision (by asking the resource owner or by establishing approval via other means).

create_authorization_response(redirect_uri, grant_user)

If the resource owner grants the access request, the authorization server issues an authorization code and delivers it to the client by adding the following parameters to the query component of the redirection URI using the “application/x-www-form-urlencoded” format. Per Section 4.1.2.

code

REQUIRED. The authorization code generated by the authorization server. The authorization code MUST expire shortly after it is issued to mitigate the risk of leaks. A maximum authorization code lifetime of 10 minutes is RECOMMENDED. The client MUST NOT use the authorization code more than once. If an authorization code is used more than once, the authorization server MUST deny the request and SHOULD revoke (when possible) all tokens previously issued based on that authorization code. The authorization code is bound to the client identifier and redirection URI.

state

REQUIRED if the “state” parameter was present in the client authorization request. The exact value received from the client.

For example, the authorization server redirects the user-agent by sending the following HTTP response.

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
       &state=xyz
Parameters
  • redirect_uri – Redirect to the given URI for the authorization

  • grant_user – if resource owner granted the request, pass this resource owner, otherwise pass None.

Returns

(status_code, body, headers)

validate_token_request()

The client makes a request to the token endpoint by sending the following parameters using the “application/x-www-form-urlencoded” format per Section 4.1.3:

grant_type

REQUIRED. Value MUST be set to “authorization_code”.

code

REQUIRED. The authorization code received from the authorization server.

redirect_uri

REQUIRED, if the “redirect_uri” parameter was included in the authorization request as described in Section 4.1.1, and their values MUST be identical.

client_id

REQUIRED, if the client is not authenticating with the authorization server as described in Section 3.2.1.

If the client type is confidential or the client was issued client credentials (or assigned other authentication requirements), the client MUST authenticate with the authorization server as described in Section 3.2.1.

For example, the client makes the following HTTP request using TLS:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
create_token_response()

If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token as described in Section 5.1. If the request client authentication failed or is invalid, the authorization server returns an error response as described in Section 5.2. Per Section 4.1.4.

An example successful response:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"example",
    "expires_in":3600,
    "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
    "example_parameter":"example_value"
}
Returns

(status_code, body, headers)

generate_authorization_code()

“The method to generate “code” value for authorization code data. Developers may rewrite this method, or customize the code length with:

class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
    AUTHORIZATION_CODE_LENGTH = 32  # default is 48
save_authorization_code(code, request)

Save authorization_code for later use. Developers MUST implement it in subclass. Here is an example:

def save_authorization_code(self, code, request):
    client = request.client
    item = AuthorizationCode(
        code=code,
        client_id=client.client_id,
        redirect_uri=request.redirect_uri,
        scope=request.scope,
        user_id=request.user.id,
    )
    item.save()
query_authorization_code(code, client)

Get authorization_code from previously savings. Developers MUST implement it in subclass:

def query_authorization_code(self, code, client):
    return Authorization.get(code=code, client_id=client.client_id)
Parameters
  • code – a string represent the code.

  • client – client related to this code.

Returns

authorization_code object

delete_authorization_code(authorization_code)

Delete authorization code from database or cache. Developers MUST implement it in subclass, e.g.:

def delete_authorization_code(self, authorization_code):
    authorization_code.delete()
Parameters

authorization_code – the instance of authorization_code

authenticate_user(authorization_code)

Authenticate the user related to this authorization_code. Developers MUST implement this method in subclass, e.g.:

def authenticate_user(self, authorization_code):
    return User.query.get(authorization_code.user_id)
Parameters

authorization_code – AuthorizationCode object

Returns

user

class authlib.oauth2.rfc6749.grants.ImplicitGrant(request, server)

The implicit grant type is used to obtain access tokens (it does not support the issuance of refresh tokens) and is optimized for public clients known to operate a particular redirection URI. These clients are typically implemented in a browser using a scripting language such as JavaScript.

Since this is a redirection-based flow, the client must be capable of interacting with the resource owner’s user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server.

Unlike the authorization code grant type, in which the client makes separate requests for authorization and for an access token, the client receives the access token as the result of the authorization request.

The implicit grant type does not include client authentication, and relies on the presence of the resource owner and the registration of the redirection URI. Because the access token is encoded into the redirection URI, it may be exposed to the resource owner and other applications residing on the same device:

+----------+
| Resource |
|  Owner   |
|          |
+----------+
     ^
     |
    (B)
+----|-----+          Client Identifier     +---------------+
|         -+----(A)-- & Redirection URI --->|               |
|  User-   |                                | Authorization |
|  Agent  -|----(B)-- User authenticates -->|     Server    |
|          |                                |               |
|          |<---(C)--- Redirection URI ----<|               |
|          |          with Access Token     +---------------+
|          |            in Fragment
|          |                                +---------------+
|          |----(D)--- Redirection URI ---->|   Web-Hosted  |
|          |          without Fragment      |     Client    |
|          |                                |    Resource   |
|     (F)  |<---(E)------- Script ---------<|               |
|          |                                +---------------+
+-|--------+
  |    |
 (A)  (G) Access Token
  |    |
  ^    v
+---------+
|         |
|  Client |
|         |
+---------+
AUTHORIZATION_ENDPOINT = True

authorization_code grant type has authorization endpoint

TOKEN_ENDPOINT_AUTH_METHODS = ['none']

Allowed client auth methods for token endpoint

validate_authorization_request()

The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the “application/x-www-form-urlencoded” format. Per Section 4.2.1.

response_type

REQUIRED. Value MUST be set to “token”.

client_id

REQUIRED. The client identifier as described in Section 2.2.

redirect_uri

OPTIONAL. As described in Section 3.1.2.

scope

OPTIONAL. The scope of the access request as described by Section 3.3.

state

RECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in Section 10.12.

The client directs the resource owner to the constructed URI using an HTTP redirection response, or by other means available to it via the user-agent.

For example, the client directs the user-agent to make the following HTTP request using TLS:

GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
create_authorization_response(redirect_uri, grant_user)

If the resource owner grants the access request, the authorization server issues an access token and delivers it to the client by adding the following parameters to the fragment component of the redirection URI using the “application/x-www-form-urlencoded” format. Per Section 4.2.2.

access_token

REQUIRED. The access token issued by the authorization server.

token_type

REQUIRED. The type of the token issued as described in Section 7.1. Value is case insensitive.

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.

scope

OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The scope of the access token as described by Section 3.3.

state

REQUIRED if the “state” parameter was present in the client authorization request. The exact value received from the client.

The authorization server MUST NOT issue a refresh token.

For example, the authorization server redirects the user-agent by sending the following HTTP response:

HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
       &state=xyz&token_type=example&expires_in=3600

Developers should note that some user-agents do not support the inclusion of a fragment component in the HTTP “Location” response header field. Such clients will require using other methods for redirecting the client than a 3xx redirection response – for example, returning an HTML page that includes a ‘continue’ button with an action linked to the redirection URI.

Parameters
  • redirect_uri – Redirect to the given URI for the authorization

  • grant_user – if resource owner granted the request, pass this resource owner, otherwise pass None.

Returns

(status_code, body, headers)

class authlib.oauth2.rfc6749.grants.ResourceOwnerPasswordCredentialsGrant(request, server)

The resource owner password credentials grant type is suitable in cases where the resource owner has a trust relationship with the client, such as the device operating system or a highly privileged

application. The authorization server should take special care when enabling this grant type and only allow it when other flows are not viable.

This grant type is suitable for clients capable of obtaining the resource owner’s credentials (username and password, typically using an interactive form). It is also used to migrate existing clients using direct authentication schemes such as HTTP Basic or Digest authentication to OAuth by converting the stored credentials to an access token:

+----------+
| Resource |
|  Owner   |
|          |
+----------+
    v
    |    Resource Owner
   (A) Password Credentials
    |
    v
+---------+                                  +---------------+
|         |>--(B)---- Resource Owner ------->|               |
|         |         Password Credentials     | Authorization |
| Client  |                                  |     Server    |
|         |<--(C)---- Access Token ---------<|               |
|         |    (w/ Optional Refresh Token)   |               |
+---------+                                  +---------------+
validate_token_request()

The client makes a request to the token endpoint by adding the following parameters using the “application/x-www-form-urlencoded” format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body:

grant_type

REQUIRED. Value MUST be set to “password”.

username

REQUIRED. The resource owner username.

password

REQUIRED. The resource owner password.

scope

OPTIONAL. The scope of the access request as described by Section 3.3.

If the client type is confidential or the client was issued client credentials (or assigned other authentication requirements), the client MUST authenticate with the authorization server as described in Section 3.2.1.

For example, the client makes the following HTTP request using transport-layer security (with extra line breaks for display purposes only):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w
create_token_response()

If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token as described in Section 5.1. If the request failed client authentication or is invalid, the authorization server returns an error response as described in Section 5.2.

An example successful response:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"example",
    "expires_in":3600,
    "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
    "example_parameter":"example_value"
}
Returns

(status_code, body, headers)

authenticate_user(username, password)

validate the resource owner password credentials using its existing password validation algorithm:

def authenticate_user(self, username, password):
    user = get_user_by_username(username)
    if user.check_password(password):
       return user
class authlib.oauth2.rfc6749.grants.ClientCredentialsGrant(request, server)

The client can request an access token using only its client credentials (or other supported means of authentication) when the client is requesting access to the protected resources under its control, or those of another resource owner that have been previously arranged with the authorization server.

The client credentials grant type MUST only be used by confidential clients:

+---------+                                  +---------------+
|         |                                  |               |
|         |>--(A)- Client Authentication --->| Authorization |
| Client  |                                  |     Server    |
|         |<--(B)---- Access Token ---------<|               |
|         |                                  |               |
+---------+                                  +---------------+

https://tools.ietf.org/html/rfc6749#section-4.4

validate_token_request()

The client makes a request to the token endpoint by adding the following parameters using the “application/x-www-form-urlencoded” format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body:

grant_type

REQUIRED. Value MUST be set to “client_credentials”.

scope

OPTIONAL. The scope of the access request as described by Section 3.3.

The client MUST authenticate with the authorization server as described in Section 3.2.1.

For example, the client makes the following HTTP request using transport-layer security (with extra line breaks for display purposes only):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

The authorization server MUST authenticate the client.

create_token_response()

If the access token request is valid and authorized, the authorization server issues an access token as described in Section 5.1. A refresh token SHOULD NOT be included. If the request failed client authentication or is invalid, the authorization server returns an error response as described in Section 5.2.

An example successful response:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"example",
    "expires_in":3600,
    "example_parameter":"example_value"
}
Returns

(status_code, body, headers)

class authlib.oauth2.rfc6749.grants.RefreshTokenGrant(request, server)

A special grant endpoint for refresh_token grant_type. Refreshing an Access Token per Section 6.

INCLUDE_NEW_REFRESH_TOKEN = False

The authorization server MAY issue a new refresh token

validate_token_request()

If the authorization server issued a refresh token to the client, the client makes a refresh request to the token endpoint by adding the following parameters using the “application/x-www-form-urlencoded” format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body, per Section 6:

grant_type

REQUIRED. Value MUST be set to “refresh_token”.

refresh_token

REQUIRED. The refresh token issued to the client.

scope

OPTIONAL. The scope of the access request as described by Section 3.3. The requested scope MUST NOT include any scope not originally granted by the resource owner, and if omitted is treated as equal to the scope originally granted by the resource owner.

For example, the client makes the following HTTP request using transport-layer security (with extra line breaks for display purposes only):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
create_token_response()

If valid and authorized, the authorization server issues an access token as described in Section 5.1. If the request failed verification or is invalid, the authorization server returns an error response as described in Section 5.2.

authenticate_refresh_token(refresh_token)

Get token information with refresh_token string. Developers MUST implement this method in subclass:

def authenticate_refresh_token(self, refresh_token):
    token = Token.get(refresh_token=refresh_token)
    if token and not token.refresh_token_revoked:
        return token
Parameters

refresh_token – The refresh token issued to the client

Returns

token

authenticate_user(credential)

Authenticate the user related to this credential. Developers MUST implement this method in subclass:

def authenticate_user(self, credential):
    return User.query.get(credential.user_id)
Parameters

credential – Token object

Returns

user

revoke_old_credential(credential)

The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client. Developers MUST implement this method in subclass:

def revoke_old_credential(self, credential):
    credential.revoked = True
    credential.save()
Parameters

credential – Token object

RFC6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage

This section contains the generic implementation of RFC6750.

Guide on Bearer Token

Bearer token is used in OAuth 2.0 framework to protect resources. You need to implement the missing methods of BearerTokenValidator before using it. Learn how to use it in Resource Server.

API Reference
class authlib.oauth2.rfc6750.BearerTokenValidator(realm=None, **extra_attributes)
authenticate_token(token_string)

A method to query token from database with the given token string. Developers MUST re-implement this method. For instance:

def authenticate_token(self, token_string):
    return get_token_from_database(token_string)
Parameters

token_string – A string to represent the access_token.

Returns

token

validate_token(token, scopes, request)

Check if token is active and matches the requested scopes.

authlib.oauth2.rfc6750.BearerToken

alias of authlib.oauth2.rfc6750.token.BearerTokenGenerator

RFC7009: OAuth 2.0 Token Revocation

This section contains the generic implementation of RFC7009.

Register Revocation Endpoint

The revocation endpoint can be easily registered to Flask OAuth 2.0 Server or Django OAuth 2.0 Server. But there are missing methods to be implemented:

from authlib.oauth2.rfc7009 import RevocationEndpoint

class MyRevocationEndpoint(RevocationEndpoint):
    def query_token(self, token, token_type_hint, client):
        q = Token.query.filter_by(client_id=client.client_id)
        if token_type_hint == 'access_token':
            return q.filter_by(access_token=token).first()
        elif token_type_hint == 'refresh_token':
            return q.filter_by(refresh_token=token).first()
        # without token_type_hint
        item = q.filter_by(access_token=token).first()
        if item:
            return item
        return q.filter_by(refresh_token=token).first()

    def revoke_token(self, token):
        token.revoked = True
        db.session.add(token)
        db.session.commit()

# register it to authorization server
authorization_server.register_endpoint(MyRevocationEndpoint)

After the registration, you can create a response with:

@app.route('/oauth/revoke', methods=['POST'])
def revoke_token():
    return server.create_endpoint_response(MyRevocationEndpoint.ENDPOINT_NAME)
API Reference
class authlib.oauth2.rfc7009.RevocationEndpoint(server)

Implementation of revocation endpoint which is described in RFC7009.

ENDPOINT_NAME = 'revocation'

Endpoint name to be registered

authenticate_token(request, client)

The client constructs the request by including the following parameters using the “application/x-www-form-urlencoded” format in the HTTP request entity-body:

token

REQUIRED. The token that the client wants to get revoked.

token_type_hint

OPTIONAL. A hint about the type of the token submitted for revocation.

create_endpoint_response(request)

Validate revocation request and create the response for revocation. For example, a client may request the revocation of a refresh token with the following request:

POST /revoke HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
Returns

(status_code, body, headers)

query_token(token_string, token_type_hint)

Get the token from database/storage by the given token string. Developers should implement this method:

def query_token(self, token_string, token_type_hint):
    if token_type_hint == 'access_token':
        return Token.query_by_access_token(token_string)
    if token_type_hint == 'refresh_token':
        return Token.query_by_refresh_token(token_string)
    return Token.query_by_access_token(token_string) or                     Token.query_by_refresh_token(token_string)
revoke_token(token, request)

Mark token as revoked. Since token MUST be unique, it would be dangerous to delete it. Consider this situation:

  1. Jane obtained a token XYZ

  2. Jane revoked (deleted) token XYZ

  3. Bob generated a new token XYZ

  4. Jane can use XYZ to access Bob’s resource

It would be secure to mark a token as revoked:

def revoke_token(self, token, request):
    hint = request.form.get('token_type_hint')
    if hint == 'access_token':
        token.access_token_revoked = True
    else:
        token.access_token_revoked = True
        token.refresh_token_revoked = True
    token.save()
authenticate_endpoint_client(request)

Authentication client for endpoint with CLIENT_AUTH_METHODS.

RFC7515: JSON Web Signature

This section contains the generic implementation of RFC7515. Find how to use it in JWS Guide.

API Reference
class authlib.jose.JsonWebSignature(algorithms=None, private_headers=None)
REGISTERED_HEADER_PARAMETER_NAMES = frozenset({'alg', 'crit', 'cty', 'jku', 'jwk', 'kid', 'typ', 'x5c', 'x5t', 'x5t#S256', 'x5u'})

Registered Header Parameter Names defined by Section 4.1

ALGORITHMS_REGISTRY = {'ES256': <authlib.jose.rfc7518.jws_algs.ECAlgorithm object>, 'ES256K': <authlib.jose.rfc7518.jws_algs.ECAlgorithm object>, 'ES384': <authlib.jose.rfc7518.jws_algs.ECAlgorithm object>, 'ES512': <authlib.jose.rfc7518.jws_algs.ECAlgorithm object>, 'EdDSA': <authlib.jose.rfc8037.jws_eddsa.EdDSAAlgorithm object>, 'HS256': <authlib.jose.rfc7518.jws_algs.HMACAlgorithm object>, 'HS384': <authlib.jose.rfc7518.jws_algs.HMACAlgorithm object>, 'HS512': <authlib.jose.rfc7518.jws_algs.HMACAlgorithm object>, 'PS256': <authlib.jose.rfc7518.jws_algs.RSAPSSAlgorithm object>, 'PS384': <authlib.jose.rfc7518.jws_algs.RSAPSSAlgorithm object>, 'PS512': <authlib.jose.rfc7518.jws_algs.RSAPSSAlgorithm object>, 'RS256': <authlib.jose.rfc7518.jws_algs.RSAAlgorithm object>, 'RS384': <authlib.jose.rfc7518.jws_algs.RSAAlgorithm object>, 'RS512': <authlib.jose.rfc7518.jws_algs.RSAAlgorithm object>, 'none': <authlib.jose.rfc7518.jws_algs.NoneAlgorithm object>}

Defined available JWS algorithms in the registry

serialize_compact(protected, payload, key)

Generate a JWS Compact Serialization. The JWS Compact Serialization represents digitally signed or MACed content as a compact, URL-safe string, per Section 7.1.

BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)
Parameters
  • protected – A dict of protected header

  • payload – A bytes/string of payload

  • key – Private key used to generate signature

Returns

byte

deserialize_compact(s, key, decode=None)

Exact JWS Compact Serialization, and validate with the given key. If key is not provided, the returned dict will contain the signature, and signing input values. Via Section 7.1.

Parameters
  • s – text of JWS Compact Serialization

  • key – key used to verify the signature

  • decode – a function to decode payload data

Returns

JWSObject

Raise

BadSignatureError

serialize_json(header_obj, payload, key)

Generate a JWS JSON Serialization. The JWS JSON Serialization represents digitally signed or MACed content as a JSON object, per Section 7.2.

Parameters
  • header_obj – A dict/list of header

  • payload – A string/dict of payload

  • key – Private key used to generate signature

Returns

JWSObject

Example header_obj of JWS JSON Serialization:

{
    "protected: {"alg": "HS256"},
    "header": {"kid": "jose"}
}

Pass a dict to generate flattened JSON Serialization, pass a list of header dict to generate standard JSON Serialization.

deserialize_json(obj, key, decode=None)

Exact JWS JSON Serialization, and validate with the given key. If key is not provided, it will return a dict without signature verification. Header will still be validated. Via Section 7.2.

Parameters
  • obj – text of JWS JSON Serialization

  • key – key used to verify the signature

  • decode – a function to decode payload data

Returns

JWSObject

Raise

BadSignatureError

serialize(header, payload, key)

Generate a JWS Serialization. It will automatically generate a Compact or JSON Serialization depending on the given header. If a header is in a JSON header format, it will call serialize_json(), otherwise it will call serialize_compact().

Parameters
  • header – A dict/list of header

  • payload – A string/dict of payload

  • key – Private key used to generate signature

Returns

byte/dict

deserialize(s, key, decode=None)

Deserialize JWS Serialization, both compact and JSON format. It will automatically deserialize depending on the given JWS.

Parameters
  • s – text of JWS Compact/JSON Serialization

  • key – key used to verify the signature

  • decode – a function to decode payload data

Returns

dict

Raise

BadSignatureError

If key is not provided, it will still deserialize the serialization without verification.

class authlib.jose.JWSHeader(protected, header)

Header object for JWS. It combine the protected header and unprotected header together. JWSHeader itself is a dict of the combined dict. e.g.

>>> protected = {'alg': 'HS256'}
>>> header = {'kid': 'a'}
>>> jws_header = JWSHeader(protected, header)
>>> print(jws_header)
{'alg': 'HS256', 'kid': 'a'}
>>> jws_header.protected == protected
>>> jws_header.header == header
Parameters
  • protected – dict of protected header

  • header – dict of unprotected header

class authlib.jose.JWSObject(header, payload, type='compact')

A dict instance to represent a JWS object.

class authlib.jose.JWSAlgorithm

Interface for JWS algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWS with this base implementation.

prepare_key(raw_data)

Prepare key for signing and verifying signature.

sign(msg, key)

Sign the text msg with a private/sign key.

Parameters
  • msg – message bytes to be signed

  • key – private key to sign the message

Returns

bytes

verify(msg, sig, key)

Verify the signature of text msg with a public/verify key.

Parameters
  • msg – message bytes to be signed

  • sig – result signature to be compared

  • key – public key to verify the signature

Returns

boolean

RFC7516: JSON Web Encryption

This section contains the generic implementation of RFC7516. Find how to use it in JWE Guide.

API Reference
class authlib.jose.JsonWebEncryption(algorithms=None, private_headers=None)
REGISTERED_HEADER_PARAMETER_NAMES = frozenset({'alg', 'crit', 'cty', 'enc', 'jku', 'jwk', 'kid', 'typ', 'x5c', 'x5t', 'x5t#S256', 'x5u', 'zip'})

Registered Header Parameter Names defined by Section 4.1

classmethod register_algorithm(algorithm)

Register an algorithm for alg or enc or zip of JWE.

serialize_compact(protected, payload, key, sender_key=None)

Generate a JWE Compact Serialization.

The JWE Compact Serialization represents encrypted content as a compact, URL-safe string. This string is:

BASE64URL(UTF8(JWE Protected Header)) || '.' ||
BASE64URL(JWE Encrypted Key) || '.' ||
BASE64URL(JWE Initialization Vector) || '.' ||
BASE64URL(JWE Ciphertext) || '.' ||
BASE64URL(JWE Authentication Tag)

Only one recipient is supported by the JWE Compact Serialization and it provides no syntax to represent JWE Shared Unprotected Header, JWE Per-Recipient Unprotected Header, or JWE AAD values.

Parameters
  • protected – A dict of protected header

  • payload – Payload (bytes or a value convertible to bytes)

  • key – Public key used to encrypt payload

  • sender_key – Sender’s private key in case JWEAlgorithmWithTagAwareKeyAgreement is used

Returns

JWE compact serialization as bytes

serialize_json(header_obj, payload, keys, sender_key=None)

Generate a JWE JSON Serialization (in fully general syntax).

The JWE JSON Serialization represents encrypted content as a JSON object. This representation is neither optimized for compactness nor URL safe.

The following members are defined for use in top-level JSON objects used for the fully general JWE JSON Serialization syntax:

protected

The “protected” member MUST be present and contain the value BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected Header value is non-empty; otherwise, it MUST be absent. These Header Parameter values are integrity protected.

unprotected

The “unprotected” member MUST be present and contain the value JWE Shared Unprotected Header when the JWE Shared Unprotected Header value is non-empty; otherwise, it MUST be absent. This value is represented as an unencoded JSON object, rather than as a string. These Header Parameter values are not integrity protected.

iv

The “iv” member MUST be present and contain the value BASE64URL(JWE Initialization Vector) when the JWE Initialization Vector value is non-empty; otherwise, it MUST be absent.

aad

The “aad” member MUST be present and contain the value BASE64URL(JWE AAD)) when the JWE AAD value is non-empty; otherwise, it MUST be absent. A JWE AAD value can be included to supply a base64url-encoded value to be integrity protected but not encrypted.

ciphertext

The “ciphertext” member MUST be present and contain the value BASE64URL(JWE Ciphertext).

tag

The “tag” member MUST be present and contain the value BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag value is non-empty; otherwise, it MUST be absent.

recipients

The “recipients” member value MUST be an array of JSON objects. Each object contains information specific to a single recipient. This member MUST be present with exactly one array element per recipient, even if some or all of the array element values are the empty JSON object “{}” (which can happen when all Header Parameter values are shared between all recipients and when no encrypted key is used, such as when doing Direct Encryption).

The following members are defined for use in the JSON objects that are elements of the “recipients” array:

header

The “header” member MUST be present and contain the value JWE Per- Recipient Unprotected Header when the JWE Per-Recipient Unprotected Header value is non-empty; otherwise, it MUST be absent. This value is represented as an unencoded JSON object, rather than as a string. These Header Parameter values are not integrity protected.

encrypted_key

The “encrypted_key” member MUST be present and contain the value BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is non-empty; otherwise, it MUST be absent.

This implementation assumes that “alg” and “enc” header fields are contained in the protected or shared unprotected header.

Parameters
  • header_obj – A dict of headers (in addition optionally contains JWE AAD)

  • payload – Payload (bytes or a value convertible to bytes)

  • keys – Public keys (or a single public key) used to encrypt payload

  • sender_key – Sender’s private key in case JWEAlgorithmWithTagAwareKeyAgreement is used

Returns

JWE JSON serialization (in fully general syntax) as dict

Example of header_obj:

{
    "protected": {
        "alg": "ECDH-1PU+A128KW",
        "enc": "A256CBC-HS512",
        "apu": "QWxpY2U",
        "apv": "Qm9iIGFuZCBDaGFybGll"
    },
    "unprotected": {
        "jku": "https://alice.example.com/keys.jwks"
    },
    "recipients": [
        {
            "header": {
                "kid": "bob-key-2"
            }
        },
        {
            "header": {
                "kid": "2021-05-06"
            }
        }
    ],
    "aad": b'Authenticate me too.'
}
serialize(header, payload, key, sender_key=None)

Generate a JWE Serialization.

It will automatically generate a compact or JSON serialization depending on header argument. If header is a dict with “protected”, “unprotected” and/or “recipients” keys, it will call serialize_json, otherwise it will call serialize_compact.

Parameters
  • header – A dict of header(s)

  • payload – Payload (bytes or a value convertible to bytes)

  • key – Public key(s) used to encrypt payload

  • sender_key – Sender’s private key in case JWEAlgorithmWithTagAwareKeyAgreement is used

Returns

JWE compact serialization as bytes or JWE JSON serialization as dict

deserialize_compact(s, key, decode=None, sender_key=None)

Extract JWE Compact Serialization.

Parameters
  • s – JWE Compact Serialization as bytes

  • key – Private key used to decrypt payload (optionally can be a tuple of kid and essentially key)

  • decode – Function to decode payload data

  • sender_key – Sender’s public key in case JWEAlgorithmWithTagAwareKeyAgreement is used

Returns

dict with header and payload keys where header value is a dict containing protected header fields

deserialize_json(obj, key, decode=None, sender_key=None)

Extract JWE JSON Serialization.

Parameters
  • obj – JWE JSON Serialization as dict or str

  • key – Private key used to decrypt payload (optionally can be a tuple of kid and essentially key)

  • decode – Function to decode payload data

  • sender_key – Sender’s public key in case JWEAlgorithmWithTagAwareKeyAgreement is used

Returns

dict with header and payload keys where header value is a dict containing protected, unprotected, recipients and/or aad keys

deserialize(obj, key, decode=None, sender_key=None)

Extract a JWE Serialization.

It supports both compact and JSON serialization.

Parameters
  • obj – JWE compact serialization as bytes or JWE JSON serialization as dict or str

  • key – Private key used to decrypt payload (optionally can be a tuple of kid and essentially key)

  • decode – Function to decode payload data

  • sender_key – Sender’s public key in case JWEAlgorithmWithTagAwareKeyAgreement is used

Returns

dict with header and payload keys

static parse_json(obj)

Parse JWE JSON Serialization.

Parameters

obj – JWE JSON Serialization as str or dict

Returns

Parsed JWE JSON Serialization as dict if obj is an str, or obj as is if obj is already a dict

class authlib.jose.JWEAlgorithm

Interface for JWE algorithm conforming to RFC7518. JWA specification (RFC7518) SHOULD implement the algorithms for JWE with this base implementation.

class authlib.jose.JWEEncAlgorithm
encrypt(msg, aad, iv, key)

Encrypt the given “msg” text.

Parameters
  • msg – text to be encrypt in bytes

  • aad – additional authenticated data in bytes

  • iv – initialization vector in bytes

  • key – encrypted key in bytes

Returns

(ciphertext, tag)

decrypt(ciphertext, aad, iv, tag, key)

Decrypt the given cipher text.

Parameters
  • ciphertext – ciphertext in bytes

  • aad – additional authenticated data in bytes

  • iv – initialization vector in bytes

  • tag – authentication tag in bytes

  • key – encrypted key in bytes

Returns

message

class authlib.jose.JWEZipAlgorithm

RFC7517: JSON Web Key

This section contains the generic implementation of RFC7517. Find how to use it in JWK Guide.

API Reference
class authlib.jose.JsonWebKey
classmethod generate_key(kty, crv_or_size, options=None, is_private=False)

Generate a Key with the given key type, curve name or bit size.

Parameters
  • kty – string of oct, RSA, EC, OKP

  • crv_or_size – curve name or bit size

  • options – a dict of other options for Key

  • is_private – create a private key or public key

Returns

Key instance

classmethod import_key(raw, options=None)

Import a Key from bytes, string, PEM or dict.

Returns

Key instance

classmethod import_key_set(raw)

Import KeySet from string, dict or a list of keys.

Returns

KeySet instance

class authlib.jose.Key(options=None)

This is the base class for a JSON Web Key.

check_key_op(operation)

Check if the given key_op is supported by this key.

Parameters

operation – key operation value, such as “sign”, “encrypt”.

Raise

ValueError

as_json(is_private=False, **params)

Represent this key as a JSON string.

thumbprint()

Implementation of RFC7638 JSON Web Key (JWK) Thumbprint.

class authlib.jose.KeySet(keys)

This class represents a JSON Web Key Set.

as_dict(is_private=False, **params)

Represent this key as a dict of the JSON Web Key Set.

as_json(is_private=False, **params)

Represent this key set as a JSON string.

find_by_kid(kid)

Find the key matches the given kid value.

Parameters

kid – A string of kid

Returns

Key instance

Raise

ValueError

class authlib.jose.OctKey(raw_key=None, options=None)

Key class of the oct key type.

get_op_key(operation)

Get the raw key for the given key_op. This method will also check if the given key_op is supported by this key.

Parameters

operation – key operation value, such as “sign”, “encrypt”.

Returns

raw key

classmethod import_key(raw, options=None)

Import a key from bytes, string, or dict data.

classmethod generate_key(key_size=256, options=None, is_private=True)

Generate a OctKey with the given bit size.

class authlib.jose.RSAKey(private_key=None, public_key=None, options=None)

Key class of the RSA key type.

PUBLIC_KEY_CLS

alias of cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey

PRIVATE_KEY_CLS

alias of cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey

class authlib.jose.ECKey(private_key=None, public_key=None, options=None)

Key class of the EC key type.

PUBLIC_KEY_CLS

alias of cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey

PRIVATE_KEY_CLS

alias of cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey

RFC7518: JSON Web Algorithms

This section contains the generic implementation of RFC7518.

Algorithms for JWS

The interface for JWS Algorithms are all inherit from authlib.jose.JWSAlgorithm.

Find how to use them in JSON Web Signature (JWS).

HMAC with SHA-2 Functions

This section is defined by RFC7518 Section 3.2.

  1. HS256: HMAC using SHA-256

  2. HS384: HMAC using SHA-384

  3. HS512: HMAC using SHA-512

Digital Signature with RSASSA-PKCS1-v1_5

Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.3.

  1. RS256: RSASSA-PKCS1-v1_5 using SHA-256

  2. RS384: RSASSA-PKCS1-v1_5 using SHA-384

  3. RS512: RSASSA-PKCS1-v1_5 using SHA-384

Digital Signature with ECDSA

Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.4.

  1. ES256: ECDSA using P-256 and SHA-256

  2. ES384: ECDSA using P-384 and SHA-384

  3. ES512: ECDSA using P-521 and SHA-512

Digital Signature with RSASSA-PSS

Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.5.

  1. PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256

  2. PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384

  3. PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512

Algorithms for JWE

This section contains algorithms for JWE alg and enc header. For alg the interface are all inherited from authlib.jose.JWEAlgorithm. For enc, the interface are inherited from authlib.jose.JWEEncAlgorithm.

Current available algorithms for alg:

  1. dir: Direct use of a shared symmetric key

  2. RSA1_5: RSAES-PKCS1-v1_5

  3. RSA-OAEP: RSAES OAEP using default parameters

  4. RSA-OAEP-256: RSAES OAEP using SHA-256 and MGF1 with SHA-256

  5. A128KW: AES Key Wrap with default initial value using 128-bit key

  6. A192KW: AES Key Wrap with default initial value using 192-bit key

  7. A256KW: AES Key Wrap with default initial value using 256-bit key

  8. A128GCMKW: Key wrapping with AES GCM using 128-bit key

  9. A192GCMKW: Key wrapping with AES GCM using 192-bit key

  10. A256GCMKW: Key wrapping with AES GCM using 256-bit key

  11. ECDH-ES: In the Direct Key Agreement mode

  12. ECDH-ES+A128KW: using Concat KDF and CEK wrapped with A128KW

  13. ECDH-ES+A192KW: using Concat KDF and CEK wrapped with A192KW

  14. ECDH-ES+A256KW: using Concat KDF and CEK wrapped with A256KW

Current available algorithms for enc:

  1. A128CBC-HS256

  2. A192CBC-HS384

  3. A256CBC-HS512

  4. A128GCM

  5. A192GCM

  6. A256GCM

Current available algorithms for zip:

  1. DEF

Algorithms for JWK

This section defines the parameters for keys using the algorithms via RFC7518 Section 6.

Find how to use them in JSON Web Key (JWK).

RFC7519: JSON Web Token

This section contains the generic Python implementation of RFC7519. Find how to use it in JWT Guide.

API Reference
class authlib.jose.JsonWebToken(algorithms, private_headers=None)
check_sensitive_data(payload)

Check if payload contains sensitive information.

encode(header, payload, key, check=True)

Encode a JWT with the given header, payload and key.

Parameters
  • header – A dict of JWS header

  • payload – A dict to be encoded

  • key – key used to sign the signature

  • check – check if sensitive data in payload

Returns

bytes

decode(s, key, claims_cls=None, claims_options=None, claims_params=None)

Decode the JWS with the given key. This is similar with verify(), except that it will raise BadSignatureError when signature doesn’t match.

Parameters
  • s – text of JWT

  • key – key used to verify the signature

  • claims_cls – class to be used for JWT claims

  • claims_optionsoptions parameters for claims_cls

  • claims_paramsparams parameters for claims_cls

Returns

claims_cls instance

Raise

BadSignatureError

class authlib.jose.JWTClaims(payload, header, options=None, params=None)
validate(now=None, leeway=0)

Validate everything in claims payload.

validate_iss()

The “iss” (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The “iss” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

validate_sub()

The “sub” (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The “sub” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

validate_aud()

The “aud” (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the “aud” claim when this claim is present, then the JWT MUST be rejected. In the general case, the “aud” value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the “aud” value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.

validate_exp(now, leeway)

The “exp” (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the “exp” claim requires that the current date/time MUST be before the expiration date/time listed in the “exp” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

validate_nbf(now, leeway)

The “nbf” (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the “nbf” claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the “nbf” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

validate_iat(now, leeway)

The “iat” (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

validate_jti()

The “jti” (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The “jti” claim can be used to prevent the JWT from being replayed. The “jti” value is a case- sensitive string. Use of this claim is OPTIONAL.

RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants

This section contains the generic Python implementation of RFC7523.

Using JWTs as Authorization Grants

Changed in version v1.0.0: Please note that all not-implemented methods are changed.

JWT Profile for OAuth 2.0 Authorization Grants works in the same way with RFC6749 built-in grants. Which means it can be registered with register_grant().

The base class is JWTBearerGrant, you need to implement the missing methods in order to use it. Here is an example:

from authlib.jose import JsonWebKey
from authlib.oauth2.rfc7523 import JWTBearerGrant as _JWTBearerGrant

class JWTBearerGrant(_JWTBearerGrant):
    def resolve_issuer_client(self, issuer):
        # if using client_id as issuer
        return Client.objects.get(client_id=issuer)

    def resolve_client_key(self, client, headers, payload):
        # if client has `jwks` column
        key_set = JsonWebKey.import_key_set(client.jwks)

    def authenticate_user(self, subject):
        # when assertion contains `sub` value, if this `sub` is email
        return User.objects.get(email=sub)

    def has_granted_permission(self, client, user):
        # check if the client has access to user's resource.
        # for instance, we have a table `UserGrant`, which user can add client
        # to this table to record that client has granted permission
        grant = UserGrant.objects.get(client_id=client.client_id, user_id=user.id)
        if grant:
          return grant.enabled
        return False

# register grant to authorization server
authorization_server.register_grant(JWTBearerGrant)

When creating a client, authorization server will generate several key pairs. The server itself can only keep the public keys, which will be used to decode assertion value.

For client implementation, check out:

  1. AssertionSession.

  2. AssertionSession.

  3. AsyncAssertionSession.

Using JWTs for Client Authentication

In RFC6749: The OAuth 2.0 Authorization Framework, Authlib provided three built-in client authentication methods, which are none, client_secret_post and client_secret_basic. With the power of Assertion Framework, we can add more client authentication methods. In this section, Authlib provides two more options: client_secret_jwt and private_key_jwt. RFC7523 itself doesn’t define any names, these two names are defined by OpenID Connect in ClientAuthentication.

The AuthorizationServer has provided a method register_client_auth_method() to add more client authentication methods.

In Authlib, client_secret_jwt and private_key_jwt share the same API, using JWTBearerClientAssertion to create a new client authentication:

class JWTClientAuth(JWTBearerClientAssertion):
    def validate_jti(self, claims, jti):
        # validate_jti is required by OpenID Connect
        # but it is optional by RFC7523
        # use cache to validate jti value
        key = 'jti:{}-{}'.format(claims['sub'], jti)
        if cache.get(key):
            return False
        cache.set(key, 1, timeout=3600)
        return True

    def resolve_client_public_key(self, client, headers):
        if headers['alg'] == 'HS256':
            return client.client_secret
        if headers['alg'] == 'RS256':
            return client.public_key
        # you may support other ``alg`` value

authorization_server.register_client_auth_method(
    JWTClientAuth.CLIENT_AUTH_METHOD,
    JWTClientAuth('https://example.com/oauth/token')
)

The value https://example.com/oauth/token is your authorization server’s token endpoint, which is used as aud value in JWT.

Now we have added this client auth method to authorization server, but no grant types support this authentication method, you need to add it to the supported grant types too, e.g. we want to support this in authorization code grant:

from authlib.oauth2.rfc6749 import grants

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = [
        'client_secret_basic',
        JWTClientAuth.CLIENT_AUTH_METHOD,
    ]
    # ...

You may noticed that the value of CLIENT_AUTH_METHOD is client_assertion_jwt. It is not client_secret_jwt or private_key_jwt, because they have the same logic. In the above implementation:

def resolve_client_public_key(self, client, headers):
    alg = headers['alg']

If this alg is a MAC SHA like HS256, it is called client_secret_jwt, because the key used to sign a JWT is the client’s client_secret value. If this alg is RS256 or something else, it is called private_key_jwt, because client will use its private key to sign the JWT. You can set a limitation in the implementation of resolve_client_public_key to accept only HS256 alg, in this case, you can also alter CLIENT_AUTH_METHOD = 'client_secret_jwt'.

Using JWTs Client Assertion in OAuth2Session

Authlib RFC7523 provides two more client authentication methods for OAuth 2 Session:

  1. client_secret_jwt

  2. private_key_jwt

Here is an example of how to register client_secret_jwt for OAuth2Session:

from authlib.oauth2.rfc7523 import ClientSecretJWT
from authlib.integrations.requests_client import OAuth2Session

session = OAuth2Session(
    'your-client-id', 'your-client-secret',
    token_endpoint_auth_method='client_secret_jwt'
)
token_endpoint = 'https://example.com/oauth/token'
session.register_client_auth_method(ClientSecretJWT(token_endpoint))
session.fetch_token(token_endpoint)

How about private_key_jwt? It is the same as client_secret_jwt:

from authlib.oauth2.rfc7523 import PrivateKeyJWT

with open('your-private-key.pem', 'rb') as f:
    private_key = f.read()

session = OAuth2Session(
    'your-client-id', private_key,
    token_endpoint_auth_method='private_key_jwt'  # NOTICE HERE
)
token_endpoint = 'https://example.com/oauth/token'
session.register_client_auth_method(PrivateKeyJWT(token_endpoint))
session.fetch_token(token_endpoint)
API Reference
class authlib.oauth2.rfc7523.JWTBearerGrant(request, server)
CLAIMS_OPTIONS = {'aud': {'essential': True}, 'exp': {'essential': True}, 'iss': {'essential': True}}

Options for verifying JWT payload claims. Developers MAY overwrite this constant to create a more strict options.

process_assertion_claims(assertion)

Extract JWT payload claims from request “assertion”, per Section 3.1.

Parameters

assertion – assertion string value in the request

Returns

JWTClaims

Raise

InvalidGrantError

validate_token_request()

The client makes a request to the token endpoint by sending the following parameters using the “application/x-www-form-urlencoded” format per Section 2.1:

grant_type

REQUIRED. Value MUST be set to “urn:ietf:params:oauth:grant-type:jwt-bearer”.

assertion

REQUIRED. Value MUST contain a single JWT.

scope

OPTIONAL.

The following example demonstrates an access token request with a JWT as an authorization grant:

POST /token.oauth2 HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
eyJpc3Mi[...omitted for brevity...].
J9l-ZhwP[...omitted for brevity...]
create_token_response()

If valid and authorized, the authorization server issues an access token.

resolve_issuer_client(issuer)

Fetch client via “iss” in assertion claims. Developers MUST implement this method in subclass, e.g.:

def resolve_issuer_client(self, issuer):
    return Client.query_by_iss(issuer)
Parameters

issuer – “iss” value in assertion

Returns

Client instance

resolve_client_key(client, headers, payload)

Resolve client key to decode assertion data. Developers MUST implement this method in subclass. For instance, there is a “jwks” column on client table, e.g.:

def resolve_client_key(self, client, headers, payload):
    # from authlib.jose import JsonWebKey

    key_set = JsonWebKey.import_key_set(client.jwks)
    return key_set.find_by_kid(headers['kid'])
Parameters
  • client – instance of OAuth client model

  • headers – headers part of the JWT

  • payload – payload part of the JWT

Returns

authlib.jose.Key instance

authenticate_user(subject)

Authenticate user with the given assertion claims. Developers MUST implement it in subclass, e.g.:

def authenticate_user(self, subject):
    return User.get_by_sub(subject)
Parameters

subject – “sub” value in claims

Returns

User instance

has_granted_permission(client, user)

Check if the client has permission to access the given user’s resource. Developers MUST implement it in subclass, e.g.:

def has_granted_permission(self, client, user):
    permission = ClientUserGrant.query(client=client, user=user)
    return permission.granted
Parameters
  • client – instance of OAuth client model

  • user – instance of User model

Returns

bool

class authlib.oauth2.rfc7523.JWTBearerClientAssertion(token_url, validate_jti=True)

Implementation of Using JWTs for Client Authentication, which is defined by RFC7523.

CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

Value of client_assertion_type of JWTs

CLIENT_AUTH_METHOD = 'client_assertion_jwt'

Name of the client authentication method

create_claims_options()

Create a claims_options for verify JWT payload claims. Developers MAY overwrite this method to create a more strict options.

process_assertion_claims(assertion, resolve_key)

Extract JWT payload claims from request “assertion”, per Section 3.1.

Parameters
  • assertion – assertion string value in the request

  • resolve_key – function to resolve the sign key

Returns

JWTClaims

Raise

InvalidClientError

validate_jti(claims, jti)

Validate if the given jti value is used before. Developers MUST implement this method:

def validate_jti(self, claims, jti):
    key = 'jti:{}-{}'.format(claims['sub'], jti)
    if redis.get(key):
        return False
    redis.set(key, 1, ex=3600)
    return True
resolve_client_public_key(client, headers)

Resolve the client public key for verifying the JWT signature. A client may have many public keys, in this case, we can retrieve it via kid value in headers. Developers MUST implement this method:

def resolve_client_public_key(self, client, headers):
    return client.public_key
class authlib.oauth2.rfc7523.ClientSecretJWT(token_endpoint=None, claims=None, alg=None)

Authentication method for OAuth 2.0 Client. This authentication method is called client_secret_jwt, which is using client_id and client_secret constructed with JWT to identify a client.

Here is an example of use client_secret_jwt with Requests Session:

from authlib.integrations.requests_client import OAuth2Session

token_endpoint = 'https://example.com/oauth/token'
session = OAuth2Session(
    'your-client-id', 'your-client-secret',
    token_endpoint_auth_method='client_secret_jwt'
)
session.register_client_auth_method(ClientSecretJWT(token_endpoint))
session.fetch_token(token_endpoint)
Parameters
  • token_endpoint – A string URL of the token endpoint

  • claims – Extra JWT claims

class authlib.oauth2.rfc7523.PrivateKeyJWT(token_endpoint=None, claims=None, alg=None)

Authentication method for OAuth 2.0 Client. This authentication method is called private_key_jwt, which is using client_id and private_key constructed with JWT to identify a client.

Here is an example of use private_key_jwt with Requests Session:

from authlib.integrations.requests_client import OAuth2Session

token_endpoint = 'https://example.com/oauth/token'
session = OAuth2Session(
    'your-client-id', 'your-client-private-key',
    token_endpoint_auth_method='private_key_jwt'
)
session.register_client_auth_method(PrivateKeyJWT(token_endpoint))
session.fetch_token(token_endpoint)
Parameters
  • token_endpoint – A string URL of the token endpoint

  • claims – Extra JWT claims

RFC7591: OAuth 2.0 Dynamic Client Registration Protocol

This section contains the generic implementation of RFC7591. OAuth 2.0 Dynamic Client Registration Protocol allows developers creating OAuth client via API through Authorization Server.

To integrate with Authlib Flask OAuth 2.0 Server or Django OAuth 2.0 Server, developers MUST implement the missing methods of ClientRegistrationEndpoint.

Client Registration Endpoint

The client registration endpoint accepts client metadata as JSON payload via POST request. The metadata may contain a JWT software_statement value. Endpoint can choose if it support software_statement, it is not enabled by default.

Changed in version v0.15: ClientRegistrationEndpoint has a breaking change in v0.15. Method of authenticate_user is replaced by authenticate_token, and parameters in save_client is also changed.

Before register the endpoint, developers MUST implement the missing methods:

from authlib.oauth2.rfc7591 import ClientRegistrationEndpoint

class MyClientRegistrationEndpoint(ClientRegistrationEndpoint):
    def authenticate_token(self, request):
        # this method is used to find who is going to create the client
        auth_header = request.headers.get('Authorization')
        # bearer a-token-string
        bearer_token = auth_header.split()[1]
        token = Token.query.get(bearer_token)
        return token

    def save_client(self, client_info, client_metadata, request):
        client = OAuthClient(
            user_id=request.credential.user_id,
            client_id=client_info['client_id'],
            client_secret=client_info['client_secret'],
            **client_metadata,
        )
        client.save()
        return client

If developers want to support software_statement, additional methods should be implemented:

class MyClientRegistrationEndpoint(ClientRegistrationEndpoint):
    # adding this to support JWT with RS256 alg, you may change it to other alg values
    software_statement_alg_values_supported = ['RS256']

    def resolve_public_key(self, request):
        # the authenticated user's public key
        # can be a string, bytes, jwk and jwk set
        return request.user.public_jwk_set

Register this endpoint and use this endpoint in routes:

authorization_server.register_endpoint(MyClientRegistrationEndpoint)

# for Flask
@app.route('/register', methods=['POST'])
def client_registration():
    return authorization_server.create_endpoint_response('client_registration')

# for Django
from django.views.decorators.http import require_http_methods

@require_http_methods(["POST"])
def client_registration(request):
    return authorization_server.create_endpoint_response('client_registration', request)
API Reference
class authlib.oauth2.rfc7591.ClientRegistrationEndpoint(server)

The client registration endpoint is an OAuth 2.0 endpoint designed to allow a client to be registered with the authorization server.

claims_class

alias of authlib.oauth2.rfc7591.claims.ClientMetadataClaims

software_statement_alg_values_supported = None

Rewrite this value with a list to support software_statement e.g. software_statement_alg_values_supported = ['RS256']

get_claims_options()

Generate claims options validation from Authorization Server metadata.

generate_client_registration_info(client, request)

Generate `registration_client_uri and registration_access_token for RFC7592. This method returns None by default. Developers MAY rewrite this method to return registration information.

generate_client_id()

Generate client_id value. Developers MAY rewrite this method to use their own way to generate client_id.

generate_client_secret()

Generate client_secret value. Developers MAY rewrite this method to use their own way to generate client_secret.

get_server_metadata()

Return server metadata which includes supported grant types, response types and etc.

authenticate_token(request)

Authenticate current credential who is requesting to register a client. Developers MUST implement this method in subclass:

def authenticate_token(self, request):
    auth = request.headers.get('Authorization')
    return get_token_by_auth(auth)
Returns

token instance

resolve_public_key(request)

Resolve a public key for decoding software_statement. If enable_software_statement=True, developers MUST implement this method in subclass:

def resolve_public_key(self, request):
    return get_public_key_from_user(request.credential)
Returns

JWK or Key string

save_client(client_info, client_metadata, request)

Save client into database. Developers MUST implement this method in subclass:

def save_client(self, client_info, client_metadata, request):
    client = OAuthClient(
        client_id=client_info['client_id'],
        client_secret=client_info['client_secret'],
        ...
    )
    client.save()
    return client
class authlib.oauth2.rfc7591.ClientMetadataClaims(payload, header, options=None, params=None)
validate_redirect_uris()

Array of redirection URI strings for use in redirect-based flows such as the authorization code and implicit flows. As required by Section 2 of OAuth 2.0 [RFC6749], clients using flows with redirection MUST register their redirection URI values. Authorization servers that support dynamic registration for redirect-based flows MUST implement support for this metadata value.

validate_token_endpoint_auth_method()

String indicator of the requested authentication method for the token endpoint.

validate_grant_types()

Array of OAuth 2.0 grant type strings that the client can use at the token endpoint.

validate_response_types()

Array of the OAuth 2.0 response type strings that the client can use at the authorization endpoint.

validate_client_name()

Human-readable string name of the client to be presented to the end-user during authorization. If omitted, the authorization server MAY display the raw “client_id” value to the end-user instead. It is RECOMMENDED that clients always send this field. The value of this field MAY be internationalized, as described in Section 2.2.

validate_client_uri()

URL string of a web page providing information about the client. If present, the server SHOULD display this URL to the end-user in a clickable fashion. It is RECOMMENDED that clients always send this field. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.

validate_logo_uri()

URL string that references a logo for the client. If present, the server SHOULD display this image to the end-user during approval. The value of this field MUST point to a valid image file. The value of this field MAY be internationalized, as described in Section 2.2.

validate_scope()

String containing a space-separated list of scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) that the client can use when requesting access tokens. The semantics of values in this list are service specific. If omitted, an authorization server MAY register a client with a default set of scopes.

validate_contacts()

Array of strings representing ways to contact people responsible for this client, typically email addresses. The authorization server MAY make these contact addresses available to end-users for support requests for the client. See Section 6 for information on Privacy Considerations.

validate_tos_uri()

URL string that points to a human-readable terms of service document for the client that describes a contractual relationship between the end-user and the client that the end-user accepts when authorizing the client. The authorization server SHOULD display this URL to the end-user if it is provided. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.

validate_policy_uri()

URL string that points to a human-readable privacy policy document that describes how the deployment organization collects, uses, retains, and discloses personal data. The authorization server SHOULD display this URL to the end-user if it is provided. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.

validate_jwks_uri()

URL string referencing the client’s JSON Web Key (JWK) Set [RFC7517] document, which contains the client’s public keys. The value of this field MUST point to a valid JWK Set document. These keys can be used by higher-level protocols that use signing or encryption. For instance, these keys might be used by some applications for validating signed requests made to the token endpoint when using JWTs for client authentication [RFC7523]. Use of this parameter is preferred over the “jwks” parameter, as it allows for easier key rotation. The “jwks_uri” and “jwks” parameters MUST NOT both be present in the same request or response.

validate_jwks()

Client’s JSON Web Key Set [RFC7517] document value, which contains the client’s public keys. The value of this field MUST be a JSON object containing a valid JWK Set. These keys can be used by higher-level protocols that use signing or encryption. This parameter is intended to be used by clients that cannot use the “jwks_uri” parameter, such as native clients that cannot host public URLs. The “jwks_uri” and “jwks” parameters MUST NOT both be present in the same request or response.

validate_software_id()

A unique identifier string (e.g., a Universally Unique Identifier (UUID)) assigned by the client developer or software publisher used by registration endpoints to identify the client software to be dynamically registered. Unlike “client_id”, which is issued by the authorization server and SHOULD vary between instances, the “software_id” SHOULD remain the same for all instances of the client software. The “software_id” SHOULD remain the same across multiple updates or versions of the same piece of software. The value of this field is not intended to be human readable and is usually opaque to the client and authorization server.

validate_software_version()

A version identifier string for the client software identified by “software_id”. The value of the “software_version” SHOULD change on any update to the client software identified by the same “software_id”. The value of this field is intended to be compared using string equality matching and no other comparison semantics are defined by this specification. The value of this field is outside the scope of this specification, but it is not intended to be human readable and is usually opaque to the client and authorization server. The definition of what constitutes an update to client software that would trigger a change to this value is specific to the software itself and is outside the scope of this specification.

RFC7636: Proof Key for Code Exchange by OAuth Public Clients

This RFC7636 is used to improve the security of Authorization Code flow for public clients by sending extra “code_challenge” and “code_verifier” to the authorization server.

Using RFC7636 in Authorization Code Grant

In order to apply proof key for code exchange, you need to register the CodeChallenge extension to AuthorizationCodeGrant. But before that, we need to re-design our AuthorizationCode database.

The new database SHOULD contain two more columns:

  1. code_challenge: A VARCHAR

  2. code_challenge_method: A VARCHAR

And the AuthorizationCodeGrant should record the code_challenge and code_challenge_method into database in save_authorization_code method:

class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
    # YOU MAY NEED TO ADD "none" METHOD FOR AUTHORIZATION WITHOUT CLIENT SECRET
    TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post', 'none']

    def save_authorization_code(self, code, request):
        # NOTICE BELOW
        code_challenge = request.data.get('code_challenge')
        code_challenge_method = request.data.get('code_challenge_method')
        auth_code = AuthorizationCode(
            code=code,
            client_id=request.client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=request.user.id,
            code_challenge=code_challenge,
            code_challenge_method=code_challenge_method,
        )
        auth_code.save()
        return auth_code

Now you can register your AuthorizationCodeGrant with the extension:

from authlib.oauth2.rfc7636 import CodeChallenge
server.register_grant(MyAuthorizationCodeGrant, [CodeChallenge(required=True)])

If required=True, code challenge is required for authorization code flow. If required=False, it is optional, it will only valid the code challenge when clients send these parameters.

Using code_challenge in Client

Read the Code Challenge section in the Web OAuth Clients.

It is also possible to add code_challenge in OAuth2Session, consider that we already have a session:

>>> from authlib.oauth2.rfc7636 import create_s256_code_challenge
>>> code_verifier = generate_token(48)
>>> code_challenge = create_s256_code_challenge(code_verifier)
>>> uri, state = session.create_authorization_url(authorize_url, code_challenge=code_challenge, code_challenge_method='S256')
>>> # visit uri, get the response
>>> authorization_response = 'https://example.com/auth?code=42..e9&state=d..t'
>>> token = session.fetch_token(token_endpoint, authorization_response=authorization_response, code_verifier=code_verifier)
API Reference
class authlib.oauth2.rfc7636.CodeChallenge(required=True)

CodeChallenge extension to Authorization Code Grant. It is used to improve the security of Authorization Code flow for public clients by sending extra “code_challenge” and “code_verifier” to the authorization server.

The AuthorizationCodeGrant SHOULD save the code_challenge and code_challenge_method into database when save_authorization_code. Then register this extension via:

server.register_grant(
    AuthorizationCodeGrant,
    [CodeChallenge(required=True)]
)
DEFAULT_CODE_CHALLENGE_METHOD = 'plain'

defaults to “plain” if not present in the request

SUPPORTED_CODE_CHALLENGE_METHOD = ['plain', 'S256']

supported code_challenge_method

get_authorization_code_challenge(authorization_code)

Get “code_challenge” associated with this authorization code. Developers MAY re-implement it in subclass, the default logic:

def get_authorization_code_challenge(self, authorization_code):
    return authorization_code.code_challenge
Parameters

authorization_code – the instance of authorization_code

get_authorization_code_challenge_method(authorization_code)

Get “code_challenge_method” associated with this authorization code. Developers MAY re-implement it in subclass, the default logic:

def get_authorization_code_challenge_method(self, authorization_code):
    return authorization_code.code_challenge_method
Parameters

authorization_code – the instance of authorization_code

RFC7638: JSON Web Key (JWK) Thumbprint

New in version v0.15.

authlib.jose

JOSE implementation in Authlib. Tracking the status of JOSE specs at https://tools.ietf.org/wg/jose/

This RFC7638 is used for computing a hash value over a JSON Web Key (JWK). The value can be used as an identity of the JWK.

The .thumbprint method is defined on the Key class, you can use it directly:

from authlib.jose import JsonWebKey

raw = read_file('rsa.pem')
key = JsonWebKey.import_key(raw)

key.thumbprint()

If a key has no kid, you can add the value of .thumbprint() as a kid:

key['kid'] = key.thumbprint()

This method is available on every Key class, including OctKey, RSAKey, ECKey, and OKPKey.

RFC7662: OAuth 2.0 Token Introspection

This section contains the generic implementation of RFC7662. OAuth 2.0 Token Introspection is usually designed to let resource servers to know content of a token.

Register Introspection Endpoint

Changed in version v1.0.

Authlib is designed to be very extendable, with the method of .register_endpoint on AuthorizationServer, it is easy to add the introspection endpoint to the authorization server. It works on both Flask OAuth 2.0 Server and Django OAuth 2.0 Server. But first, we need to implement the missing methods:

from authlib.oauth2.rfc7662 import IntrospectionEndpoint

class MyIntrospectionEndpoint(IntrospectionEndpoint):
    def query_token(self, token, token_type_hint):
        if token_type_hint == 'access_token':
            tok = Token.query.filter_by(access_token=token).first()
        elif token_type_hint == 'refresh_token':
            tok = Token.query.filter_by(refresh_token=token).first()
        else:
            # without token_type_hint
            tok = Token.query.filter_by(access_token=token).first()
            if not tok:
                tok = Token.query.filter_by(refresh_token=token).first()
        return tok

    def introspect_token(self, token):
        return {
            'active': True,
            'client_id': token.client_id,
            'token_type': token.token_type,
            'username': get_token_username(token),
            'scope': token.get_scope(),
            'sub': get_token_user_sub(token),
            'aud': token.client_id,
            'iss': 'https://server.example.com/',
            'exp': token.expires_at,
            'iat': token.issued_at,
        }

    def check_permission(self, token, client, request):
        # for example, we only allow internal client to access introspection endpoint
        return client.client_type == 'internal'

# register it to authorization server
server.register_endpoint(MyIntrospectionEndpoint)

After the registration, we can create a response with:

@app.route('/oauth/introspect', methods=['POST'])
def introspect_token():
    return server.create_endpoint_response(MyIntrospectionEndpoint.ENDPOINT_NAME)
Use Introspection in Resource Server

New in version v1.0.

When resource server has no access to token database, it can use introspection endpoint to validate the given token. Here is how:

import requests
from authlib.oauth2.rfc7662 import IntrospectTokenValidator
from your_project import secrets

class MyIntrospectTokenValidator(IntrospectTokenValidator):
    def introspect_token(self, token_string):
        url = 'https://example.com/oauth/introspect'
        data = {'token': token_string, 'token_type_hint': 'access_token'}
        auth = (secrets.internal_client_id, secrets.internal_client_secret)
        resp = requests.post(url, data=data, auth=auth)
        resp.raise_for_status()
        return resp.json()

We can then register this token validator in to resource protector:

require_oauth = ResourceProtector()
require_oauth.register_token_validator(MyIntrospectTokenValidator())

Please note, when using IntrospectTokenValidator, the current_token will be a dict.

API Reference
class authlib.oauth2.rfc7662.IntrospectionEndpoint(server)

Implementation of introspection endpoint which is described in RFC7662.

ENDPOINT_NAME = 'introspection'

Endpoint name to be registered

authenticate_token(request, client)

The protected resource calls the introspection endpoint using an HTTP POST request with parameters sent as “application/x-www-form-urlencoded” data. The protected resource sends a parameter representing the token along with optional parameters representing additional context that is known by the protected resource to aid the authorization server in its response.

token

REQUIRED The string value of the token. For access tokens, this is the access_token value returned from the token endpoint defined in OAuth 2.0. For refresh tokens, this is the refresh_token value returned from the token endpoint as defined in OAuth 2.0.

token_type_hint

OPTIONAL A hint about the type of the token submitted for introspection.

create_endpoint_response(request)

Validate introspection request and create the response.

Returns

(status_code, body, headers)

check_permission(token, client, request)

Check if the request has permission to introspect the token. Developers MUST implement this method:

def check_permission(self, token, client, request):
    # only allow a special client to introspect the token
    return client.client_id == 'introspection_client'
Returns

bool

query_token(token_string, token_type_hint)

Get the token from database/storage by the given token string. Developers should implement this method:

def query_token(self, token_string, token_type_hint):
    if token_type_hint == 'access_token':
        tok = Token.query_by_access_token(token_string)
    elif token_type_hint == 'refresh_token':
        tok = Token.query_by_refresh_token(token_string)
    else:
        tok = Token.query_by_access_token(token_string)
        if not tok:
            tok = Token.query_by_refresh_token(token_string)
    return tok
introspect_token(token)

Read given token and return its introspection metadata as a dictionary following Section 2.2:

def introspect_token(self, token):
    return {
        'active': True,
        'client_id': token.client_id,
        'token_type': token.token_type,
        'username': get_token_username(token),
        'scope': token.get_scope(),
        'sub': get_token_user_sub(token),
        'aud': token.client_id,
        'iss': 'https://server.example.com/',
        'exp': token.expires_at,
        'iat': token.issued_at,
    }
authenticate_endpoint_client(request)

Authentication client for endpoint with CLIENT_AUTH_METHODS.

class authlib.oauth2.rfc7662.IntrospectTokenValidator(realm=None, **extra_attributes)
authenticate_token(token_string)

A method to query token from database with the given token string. Developers MUST re-implement this method. For instance:

def authenticate_token(self, token_string):
    return get_token_from_database(token_string)
Parameters

token_string – A string to represent the access_token.

Returns

token

introspect_token(token_string)

Request introspection token endpoint with the given token string, authorization server will return token information in JSON format. Developers MUST implement this method before using it:

def introspect_token(self, token_string):
    # for example, introspection token endpoint has limited
    # internal IPs to access, so there is no need to add
    # authentication.
    url = 'https://example.com/oauth/introspect'
    resp = requests.post(url, data={'token': token_string})
    resp.raise_for_status()
    return resp.json()
validate_token(token, scopes, request)

A method to validate if the authorized token is valid, if it has the permission on the given scopes. Developers MUST re-implement this method. e.g, check if token is expired, revoked:

def validate_token(self, token, scopes, request):
    if not token:
        raise InvalidTokenError()
    if token.is_expired() or token.is_revoked():
        raise InvalidTokenError()
    if not match_token_scopes(token, scopes):
        raise InsufficientScopeError()

RFC8037: CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE)

New in version v0.15.

Algorithms for JWS

In RFC8037, algorithm “EdDSA” is defined for JWS. Use Edwards-curve Digital Signature Algorithm (EdDSA) for signing data using “JSON Web Signature (JWS)”:

from authlib.jose import JsonWebSignature

# only allow "EdDSA" alg value
jws = JsonWebSignature(algorithms=['EdDSA'])

protected = {'alg': 'EdDSA'}
payload = b'example'

with open('ed25519-pkcs8.pem', 'rb') as f:
    secret = f.read()
jws.serialize_compact(protected, payload, secret)

Learn how to use other JWS functions at JSON Web Signature (JWS).

It can also be used in JSON Web Token (JWT):

from authlib.jose import JsonWebToken

jwt = JsonWebToken(algorithms=['EdDSA'])

with open('ed25519-pkcs8.pem', 'rb') as f:
    key = f.read()

header = {'alg': 'EdDSA'}
payload = {'iss': 'Authlib', 'sub': '123', ...}
s = jwt.encode(header, payload, key)
Algorithms for JWK

In RFC8037, algorithms “Ed25519”, “Ed448”, “X25519”, “X448” are defined for JWK. Loads and dumps Json Web Keys with:

from authlib.jose import JsonWebKey

with open('ed25519-pkcs8.pem', 'rb') as f:
    key = f.read()

# MUST use "OKP" as "kty" value
JsonWebKey.import_key(key, {'kty': 'OKP'})

Learn how to use other JWK functions at JSON Web Key (JWK).

Algorithms for JWE

“X25519”, “X448” keys are used in “epk” for ECDH-ES algorithms. Just use the X25519 and X448 key for ECDH-ES in JWE:

from authlib.jose import OKPKey
from authlib.jose import JsonWebEncryption

jwe = JsonWebEncryption()

with open('X25519.pem', 'rb') as f:
    key = OKPKey.import_key(f.read())

protected = {
    "alg": "ECDH-ES",
    "enc": "A128GCM",
    "apu": "QWxpY2U",
    "apv": "Qm9i",
}
jwe.serialize_compact(protected, b'hello', key)
API Reference
class authlib.jose.OKPKey(private_key=None, public_key=None, options=None)

Key class of the OKP key type.

RFC8414: OAuth 2.0 Authorization Server Metadata

API Reference
class authlib.oauth2.rfc8414.AuthorizationServerMetadata

Define Authorization Server Metadata via Section 2 in RFC8414.

validate_issuer()

REQUIRED. The authorization server’s issuer identifier, which is a URL that uses the “https” scheme and has no query or fragment components.

validate_authorization_endpoint()

URL of the authorization server’s authorization endpoint [RFC6749]. This is REQUIRED unless no grant types are supported that use the authorization endpoint.

validate_token_endpoint()

URL of the authorization server’s token endpoint [RFC6749]. This is REQUIRED unless only the implicit grant type is supported.

validate_jwks_uri()

OPTIONAL. URL of the authorization server’s JWK Set [JWK] document. The referenced document contains the signing key(s) the client uses to validate signatures from the authorization server. This URL MUST use the “https” scheme. The JWK Set MAY also contain the server’s encryption key or keys, which are used by clients to encrypt requests to the server. When both signing and encryption keys are made available, a “use” (public key use) parameter value is REQUIRED for all keys in the referenced JWK Set to indicate each key’s intended usage.

validate_registration_endpoint()

OPTIONAL. URL of the authorization server’s OAuth 2.0 Dynamic Client Registration endpoint [RFC7591].

validate_scopes_supported()

RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] “scope” values that this authorization server supports. Servers MAY choose not to advertise some supported scope values even when this parameter is used.

validate_response_types_supported()

REQUIRED. JSON array containing a list of the OAuth 2.0 “response_type” values that this authorization server supports. The array values used are the same as those used with the “response_types” parameter defined by “OAuth 2.0 Dynamic Client Registration Protocol” [RFC7591].

validate_response_modes_supported()

OPTIONAL. JSON array containing a list of the OAuth 2.0 “response_mode” values that this authorization server supports, as specified in “OAuth 2.0 Multiple Response Type Encoding Practices” [OAuth.Responses]. If omitted, the default is “[“query”, “fragment”]”. The response mode value “form_post” is also defined in “OAuth 2.0 Form Post Response Mode” [OAuth.Post].

validate_grant_types_supported()

OPTIONAL. JSON array containing a list of the OAuth 2.0 grant type values that this authorization server supports. The array values used are the same as those used with the “grant_types” parameter defined by “OAuth 2.0 Dynamic Client Registration Protocol” [RFC7591]. If omitted, the default value is “[“authorization_code”, “implicit”]”.

validate_token_endpoint_auth_methods_supported()

OPTIONAL. JSON array containing a list of client authentication methods supported by this token endpoint. Client authentication method values are used in the “token_endpoint_auth_method” parameter defined in Section 2 of [RFC7591]. If omitted, the default is “client_secret_basic” – the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749].

validate_token_endpoint_auth_signing_alg_values_supported()

OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the token endpoint for the signature on the JWT [JWT] used to authenticate the client at the token endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “token_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. Servers SHOULD support “RS256”. The value “none” MUST NOT be used.

validate_service_documentation()

OPTIONAL. URL of a page containing human-readable information that developers might want or need to know when using the authorization server. In particular, if the authorization server does not support Dynamic Client Registration, then information on how to register clients needs to be provided in this documentation.

validate_ui_locales_supported()

OPTIONAL. Languages and scripts supported for the user interface, represented as a JSON array of language tag values from BCP 47 [RFC5646]. If omitted, the set of supported languages and scripts is unspecified.

validate_op_policy_uri()

OPTIONAL. URL that the authorization server provides to the person registering the client to read about the authorization server’s requirements on how the client can use the data provided by the authorization server. The registration process SHOULD display this URL to the person registering the client if it is given. As described in Section 5, despite the identifier “op_policy_uri” appearing to be OpenID-specific, its usage in this specification is actually referring to a general OAuth 2.0 feature that is not specific to OpenID Connect.

validate_op_tos_uri()

OPTIONAL. URL that the authorization server provides to the person registering the client to read about the authorization server’s terms of service. The registration process SHOULD display this URL to the person registering the client if it is given. As described in Section 5, despite the identifier “op_tos_uri”, appearing to be OpenID-specific, its usage in this specification is actually referring to a general OAuth 2.0 feature that is not specific to OpenID Connect.

validate_revocation_endpoint()

OPTIONAL. URL of the authorization server’s OAuth 2.0 revocation endpoint [RFC7009].

validate_revocation_endpoint_auth_methods_supported()

OPTIONAL. JSON array containing a list of client authentication methods supported by this revocation endpoint. The valid client authentication method values are those registered in the IANA “OAuth Token Endpoint Authentication Methods” registry [IANA.OAuth.Parameters]. If omitted, the default is “client_secret_basic” – the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749].

validate_revocation_endpoint_auth_signing_alg_values_supported()

OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the revocation endpoint for the signature on the JWT [JWT] used to authenticate the client at the revocation endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “revocation_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. The value “none” MUST NOT be used.

validate_introspection_endpoint()

OPTIONAL. URL of the authorization server’s OAuth 2.0 introspection endpoint [RFC7662].

validate_introspection_endpoint_auth_methods_supported()

OPTIONAL. JSON array containing a list of client authentication methods supported by this introspection endpoint. The valid client authentication method values are those registered in the IANA “OAuth Token Endpoint Authentication Methods” registry [IANA.OAuth.Parameters] or those registered in the IANA “OAuth Access Token Types” registry [IANA.OAuth.Parameters]. (These values are and will remain distinct, due to Section 7.2.) If omitted, the set of supported authentication methods MUST be determined by other means.

validate_introspection_endpoint_auth_signing_alg_values_supported()

OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the introspection endpoint for the signature on the JWT [JWT] used to authenticate the client at the introspection endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “introspection_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. The value “none” MUST NOT be used.

validate_code_challenge_methods_supported()

OPTIONAL. JSON array containing a list of Proof Key for Code Exchange (PKCE) [RFC7636] code challenge methods supported by this authorization server. Code challenge method values are used in the “code_challenge_method” parameter defined in Section 4.3 of [RFC7636]. The valid code challenge method values are those registered in the IANA “PKCE Code Challenge Methods” registry [IANA.OAuth.Parameters]. If omitted, the authorization server does not support PKCE.

validate()

Validate all server metadata value.

RFC8628: OAuth 2.0 Device Authorization Grant

This section contains the generic implementation of RFC8628. OAuth 2.0 Device Authorization Grant is usually used when devices have limited input capabilities or lack a suitable browser, such as smart TVs, media consoles, picture frames, printers and etc.

To integrate with Authlib Flask OAuth 2.0 Server or Django OAuth 2.0 Server, developers MUST implement the missing methods of the two classes:

  1. DeviceAuthorizationEndpoint

  2. DeviceCodeGrant

Device Authorization Endpoint

There are two missing methods that developers MUST implement:

from authlib.oauth2.rfc8628 import DeviceAuthorizationEndpoint

class MyDeviceAuthorizationEndpoint(DeviceAuthorizationEndpoint):
    def get_verification_uri(self):
        return 'https://example.com/active'

    def save_device_credential(self, client_id, scope, data):
        credential = DeviceCredential(
            client_id=client_id,
            scope=scope,
            **data
        )
        credential.save()

# register it to authorization server
authorization_server.register_endpoint(MyDeviceAuthorizationEndpoint)

get_verification_uri is the URL that end user will use their browser to log in and authenticate. See below “Verification Endpoint”.

After the registration, you can create a response with:

@app.route('/device_authorization', methods=['POST'])
def device_authorization():
    return server.create_endpoint_response('device_authorization')
Device Code Grant

With Authlib .register_grant, we can add DeviceCodeGrant easily. But first, we need to implement the missing methods:

from authlib.oauth2.rfc8628 import DeviceCodeGrant

class MyDeviceCodeGrant(DeviceCodeGrant):
    def query_device_credential(self, device_code):
        return DeviceCredential.query(device_code=device_code)

    def query_user_grant(self, user_code):
        data = redis.get('oauth_user_grant:' + user_code)
        if not data:
            return None

        user_id, allowed = data.split()
        user = User.query.get(user_id)
        return user, bool(allowed)

    def should_slow_down(self, credential, now):
        # developers can return True/False based on credential and now
        return False

authorization_server.register_grant(MyDeviceCodeGrant)

Note query_user_grant, we are fetching data from redis. This data was saved from verification endpoint when end user granted the request.

Verification Endpoint

Developers MUST implement this part by themselves. Here is a hint on how to implement this endpoint:

@app.route('/active', methods=['GET', 'POST'])
@login_required
def verify_device_code():
    if request.method == 'GET':
        return render_template('verification.html')

    allowed = request.form['allowed']
    user_code = request.form['user_code']
    key = 'oauth_user_grant:' + user_code
    redis.set(key, f'{current_user.id} {allowed}', 12)
    return render_template('verification.html')

Check points:

  1. route should match get_verification_uri in Device Authorization Endpoint

  2. user grant should match query_user_grant in Device Code Grant

API Reference
class authlib.oauth2.rfc8628.DeviceAuthorizationEndpoint(server)

This OAuth 2.0 [RFC6749] protocol extension enables OAuth clients to request user authorization from applications on devices that have limited input capabilities or lack a suitable browser. Such devices include smart TVs, media consoles, picture frames, and printers, which lack an easy input method or a suitable browser required for traditional OAuth interactions. Here is the authorization flow:

+----------+                                +----------------+
|          |>---(A)-- Client Identifier --->|                |
|          |                                |                |
|          |<---(B)-- Device Code,      ---<|                |
|          |          User Code,            |                |
|  Device  |          & Verification URI    |                |
|  Client  |                                |                |
|          |  [polling]                     |                |
|          |>---(E)-- Device Code       --->|                |
|          |          & Client Identifier   |                |
|          |                                |  Authorization |
|          |<---(F)-- Access Token      ---<|     Server     |
+----------+   (& Optional Refresh Token)   |                |
      v                                     |                |
      :                                     |                |
     (C) User Code & Verification URI       |                |
      :                                     |                |
      v                                     |                |
+----------+                                |                |
| End User |                                |                |
|    at    |<---(D)-- End user reviews  --->|                |
|  Browser |          authorization request |                |
+----------+                                +----------------+

This DeviceAuthorizationEndpoint is the implementation of step (A) and (B).

  1. The client requests access from the authorization server and includes its client identifier in the request.

  2. The authorization server issues a device code and an end-user code and provides the end-user verification URI.

USER_CODE_TYPE = 'string'

customize “user_code” type, string or digital

EXPIRES_IN = 1800

The lifetime in seconds of the “device_code” and “user_code”

INTERVAL = 5

The minimum amount of time in seconds that the client SHOULD wait between polling requests to the token endpoint.

authenticate_client(request)

client_id is REQUIRED if the client is not authenticating with the authorization server as described in Section 3.2.1. of [RFC6749].

This means the endpoint support “none” authentication method. In this case, this endpoint’s auth methods are:

  • client_secret_basic

  • client_secret_post

  • none

Developers change the value of CLIENT_AUTH_METHODS in subclass. For instance:

class MyDeviceAuthorizationEndpoint(DeviceAuthorizationEndpoint):
    # only support ``client_secret_basic`` auth method
    CLIENT_AUTH_METHODS = ['client_secret_basic']
generate_user_code()

A method to generate user_code value for device authorization endpoint. This method will generate a random string like MQNA-JPOZ. Developers can rewrite this method to create their own user_code.

generate_device_code()

A method to generate device_code value for device authorization endpoint. This method will generate a random string of 42 characters. Developers can rewrite this method to create their own device_code.

get_verification_uri()

Define the verification_uri of device authorization endpoint. Developers MUST implement this method in subclass:

def get_verification_uri(self):
    return 'https://your-company.com/active'
save_device_credential(client_id, scope, data)

Save device token into database for later use. Developers MUST implement this method in subclass:

def save_device_credential(self, client_id, scope, data):
    item = DeviceCredential(
        client_id=client_id,
        scope=scope,
        **data
    )
    item.save()
class authlib.oauth2.rfc8628.DeviceCodeGrant(request, server)

This OAuth 2.0 [RFC6749] protocol extension enables OAuth clients to request user authorization from applications on devices that have limited input capabilities or lack a suitable browser. Such devices include smart TVs, media consoles, picture frames, and printers, which lack an easy input method or a suitable browser required for traditional OAuth interactions. Here is the authorization flow:

+----------+                                +----------------+
|          |>---(A)-- Client Identifier --->|                |
|          |                                |                |
|          |<---(B)-- Device Code,      ---<|                |
|          |          User Code,            |                |
|  Device  |          & Verification URI    |                |
|  Client  |                                |                |
|          |  [polling]                     |                |
|          |>---(E)-- Device Code       --->|                |
|          |          & Client Identifier   |                |
|          |                                |  Authorization |
|          |<---(F)-- Access Token      ---<|     Server     |
+----------+   (& Optional Refresh Token)   |                |
      v                                     |                |
      :                                     |                |
     (C) User Code & Verification URI       |                |
      :                                     |                |
      v                                     |                |
+----------+                                |                |
| End User |                                |                |
|    at    |<---(D)-- End user reviews  --->|                |
|  Browser |          authorization request |                |
+----------+                                +----------------+

This DeviceCodeGrant is the implementation of step (E) and (F).

  1. While the end user reviews the client’s request (step D), the client repeatedly polls the authorization server to find out if the user completed the user authorization step. The client includes the device code and its client identifier.

  2. The authorization server validates the device code provided by the client and responds with the access token if the client is granted access, an error if they are denied access, or an indication that the client should continue to poll.

validate_token_request()

After displaying instructions to the user, the client creates an access token request and sends it to the token endpoint with the following parameters:

grant_type

REQUIRED. Value MUST be set to “urn:ietf:params:oauth:grant-type:device_code”.

device_code

REQUIRED. The device verification code, “device_code” from the device authorization response.

client_id

REQUIRED if the client is not authenticating with the authorization server as described in Section 3.2.1. of [RFC6749]. The client identifier as described in Section 2.2 of [RFC6749].

For example, the client makes the following HTTPS request:

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=1406020730
create_token_response()

If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token.

query_device_credential(device_code)

Get device credential from previously savings via DeviceAuthorizationEndpoint. Developers MUST implement it in subclass:

def query_device_credential(self, device_code):
    return DeviceCredential.query.get(device_code)
Parameters

device_code – a string represent the code.

Returns

DeviceCredential instance

query_user_grant(user_code)

Get user and grant via the given user code. Developers MUST implement it in subclass:

def query_user_grant(self, user_code):
    # e.g. we saved user grant info in redis
    data = redis.get('oauth_user_grant:' + user_code)
    if not data:
        return None

    user_id, allowed = data.split()
    user = User.query.get(user_id)
    return user, bool(allowed)

Note, user grant information is saved by verification endpoint.

should_slow_down(credential)

The authorization request is still pending and polling should continue, but the interval MUST be increased by 5 seconds for this and all subsequent requests.

authenticate_token_endpoint_client()

Authenticate client with the given methods for token endpoint.

For example, the client makes the following HTTP request using TLS:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

Default available methods are: “none”, “client_secret_basic” and “client_secret_post”.

Returns

client

save_token(token)

A method to save token into database.

validate_requested_scope()

Validate if requested scope is supported by Authorization Server.

OpenID Connect 1.0

This part of the documentation covers the specification of OpenID Connect. Learn how to use it in Flask OIDC Provider and Django OIDC Provider.

OpenID Grants
class authlib.oidc.core.grants.OpenIDToken

Bases: object

generate_user_info(user, scope)

Provide user information for the given scope. Developers MUST implement this method in subclass, e.g.:

from authlib.oidc.core import UserInfo

def generate_user_info(self, user, scope):
    user_info = UserInfo(sub=user.id, name=user.name)
    if 'email' in scope:
        user_info['email'] = user.email
    return user_info
Parameters
  • user – user instance

  • scope – scope of the token

Returns

authlib.oidc.core.UserInfo instance

get_audiences(request)

Parse aud value for id_token, default value is client id. Developers MAY rewrite this method to provide a customized audience value.

get_jwt_config(grant)

Get the JWT configuration for OpenIDCode extension. The JWT configuration will be used to generate id_token. Developers MUST implement this method in subclass, e.g.:

def get_jwt_config(self, grant):
    return {
        'key': read_private_key_file(key_path),
        'alg': 'RS256',
        'iss': 'issuer-identity',
        'exp': 3600
    }
Parameters

grant – AuthorizationCodeGrant instance

Returns

dict

class authlib.oidc.core.grants.OpenIDCode(require_nonce=False)

Bases: authlib.oidc.core.grants.code.OpenIDToken

An extension from OpenID Connect for “grant_type=code” request. Developers MUST implement the missing methods:

class MyOpenIDCode(OpenIDCode):
    def get_jwt_config(self, grant):
        return {...}

    def exists_nonce(self, nonce, request):
        return check_if_nonce_in_cache(request.client_id, nonce)

    def generate_user_info(self, user, scope):
        return {...}

The register this extension with AuthorizationCodeGrant:

authorization_server.register_grant(AuthorizationCodeGrant, extensions=[MyOpenIDCode()])
exists_nonce(nonce, request)

Check if the given nonce is existing in your database. Developers MUST implement this method in subclass, e.g.:

def exists_nonce(self, nonce, request):
    exists = AuthorizationCode.query.filter_by(
        client_id=request.client_id, nonce=nonce
    ).first()
    return bool(exists)
Parameters
  • nonce – A string of “nonce” parameter in request

  • request – OAuth2Request instance

Returns

Boolean

class authlib.oidc.core.grants.OpenIDImplicitGrant(request, server)

Bases: authlib.oauth2.rfc6749.grants.implicit.ImplicitGrant

create_authorization_response(redirect_uri, grant_user)

If the resource owner grants the access request, the authorization server issues an access token and delivers it to the client by adding the following parameters to the fragment component of the redirection URI using the “application/x-www-form-urlencoded” format. Per Section 4.2.2.

access_token

REQUIRED. The access token issued by the authorization server.

token_type

REQUIRED. The type of the token issued as described in Section 7.1. Value is case insensitive.

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.

scope

OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The scope of the access token as described by Section 3.3.

state

REQUIRED if the “state” parameter was present in the client authorization request. The exact value received from the client.

The authorization server MUST NOT issue a refresh token.

For example, the authorization server redirects the user-agent by sending the following HTTP response:

HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
       &state=xyz&token_type=example&expires_in=3600

Developers should note that some user-agents do not support the inclusion of a fragment component in the HTTP “Location” response header field. Such clients will require using other methods for redirecting the client than a 3xx redirection response – for example, returning an HTML page that includes a ‘continue’ button with an action linked to the redirection URI.

Parameters
  • redirect_uri – Redirect to the given URI for the authorization

  • grant_user – if resource owner granted the request, pass this resource owner, otherwise pass None.

Returns

(status_code, body, headers)

exists_nonce(nonce, request)

Check if the given nonce is existing in your database. Developers should implement this method in subclass, e.g.:

def exists_nonce(self, nonce, request):
    exists = AuthorizationCode.query.filter_by(
        client_id=request.client_id, nonce=nonce
    ).first()
    return bool(exists)
Parameters
  • nonce – A string of “nonce” parameter in request

  • request – OAuth2Request instance

Returns

Boolean

generate_user_info(user, scope)

Provide user information for the given scope. Developers MUST implement this method in subclass, e.g.:

from authlib.oidc.core import UserInfo

def generate_user_info(self, user, scope):
    user_info = UserInfo(sub=user.id, name=user.name)
    if 'email' in scope:
        user_info['email'] = user.email
    return user_info
Parameters
  • user – user instance

  • scope – scope of the token

Returns

authlib.oidc.core.UserInfo instance

get_audiences(request)

Parse aud value for id_token, default value is client id. Developers MAY rewrite this method to provide a customized audience value.

get_jwt_config()

Get the JWT configuration for OpenIDImplicitGrant. The JWT configuration will be used to generate id_token. Developers MUST implement this method in subclass, e.g.:

def get_jwt_config(self):
    return {
        'key': read_private_key_file(key_path),
        'alg': 'RS256',
        'iss': 'issuer-identity',
        'exp': 3600
    }
Returns

dict

validate_authorization_request()

The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the “application/x-www-form-urlencoded” format. Per Section 4.2.1.

response_type

REQUIRED. Value MUST be set to “token”.

client_id

REQUIRED. The client identifier as described in Section 2.2.

redirect_uri

OPTIONAL. As described in Section 3.1.2.

scope

OPTIONAL. The scope of the access request as described by Section 3.3.

state

RECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in Section 10.12.

The client directs the resource owner to the constructed URI using an HTTP redirection response, or by other means available to it via the user-agent.

For example, the client directs the user-agent to make the following HTTP request using TLS:

GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
class authlib.oidc.core.grants.OpenIDHybridGrant(request, server)

Bases: authlib.oidc.core.grants.implicit.OpenIDImplicitGrant

AUTHORIZATION_CODE_LENGTH = 48

Generated “code” length

generate_authorization_code()

“The method to generate “code” value for authorization code data. Developers may rewrite this method, or customize the code length with:

class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
    AUTHORIZATION_CODE_LENGTH = 32  # default is 48
save_authorization_code(code, request)

Save authorization_code for later use. Developers MUST implement it in subclass. Here is an example:

def save_authorization_code(self, code, request):
    client = request.client
    auth_code = AuthorizationCode(
        code=code,
        client_id=client.client_id,
        redirect_uri=request.redirect_uri,
        scope=request.scope,
        nonce=request.data.get('nonce'),
        user_id=request.user.id,
    )
    auth_code.save()
validate_authorization_request()

The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the “application/x-www-form-urlencoded” format. Per Section 4.2.1.

response_type

REQUIRED. Value MUST be set to “token”.

client_id

REQUIRED. The client identifier as described in Section 2.2.

redirect_uri

OPTIONAL. As described in Section 3.1.2.

scope

OPTIONAL. The scope of the access request as described by Section 3.3.

state

RECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in Section 10.12.

The client directs the resource owner to the constructed URI using an HTTP redirection response, or by other means available to it via the user-agent.

For example, the client directs the user-agent to make the following HTTP request using TLS:

GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
OpenID Claims
class authlib.oidc.core.IDToken(payload, header, options=None, params=None)

Bases: authlib.jose.rfc7519.claims.JWTClaims

validate(now=None, leeway=0)

Validate everything in claims payload.

validate_acr()

OPTIONAL. Authentication Context Class Reference. String specifying an Authentication Context Class Reference value that identifies the Authentication Context Class that the authentication performed satisfied. The value “0” indicates the End-User authentication did not meet the requirements of ISO/IEC 29115 level 1. Authentication using a long-lived browser cookie, for instance, is one example where the use of “level 0” is appropriate. Authentications with level 0 SHOULD NOT be used to authorize access to any resource of any monetary value. An absolute URI or an RFC 6711 registered name SHOULD be used as the acr value; registered names MUST NOT be used with a different meaning than that which is registered. Parties using this claim will need to agree upon the meanings of the values used, which may be context-specific. The acr value is a case sensitive string.

validate_amr()

OPTIONAL. Authentication Methods References. JSON array of strings that are identifiers for authentication methods used in the authentication. For instance, values might indicate that both password and OTP authentication methods were used. The definition of particular values to be used in the amr Claim is beyond the scope of this specification. Parties using this claim will need to agree upon the meanings of the values used, which may be context-specific. The amr value is an array of case sensitive strings.

validate_at_hash()

OPTIONAL. Access Token hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token’s JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string.

validate_auth_time()

Time when the End-User authentication occurred. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time. When a max_age request is made or when auth_time is requested as an Essential Claim, then this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL.

validate_azp()

OPTIONAL. Authorized party - the party to which the ID Token was issued. If present, it MUST contain the OAuth 2.0 Client ID of this party. This Claim is only needed when the ID Token has a single audience value and that audience is different than the authorized party. It MAY be included even when the authorized party is the same as the sole audience. The azp value is a case sensitive string containing a StringOrURI value.

validate_nonce()

String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token. If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request. If present in the Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token with the Claim Value being the nonce value sent in the Authentication Request. Authorization Servers SHOULD perform no other processing on nonce values used. The nonce value is a case sensitive string.

class authlib.oidc.core.CodeIDToken(payload, header, options=None, params=None)

Bases: authlib.oidc.core.claims.IDToken

class authlib.oidc.core.ImplicitIDToken(payload, header, options=None, params=None)

Bases: authlib.oidc.core.claims.IDToken

validate_at_hash()

If the ID Token is issued from the Authorization Endpoint with an access_token value, which is the case for the response_type value id_token token, this is REQUIRED; it MAY NOT be used when no Access Token is issued, which is the case for the response_type value id_token.

class authlib.oidc.core.HybridIDToken(payload, header, options=None, params=None)

Bases: authlib.oidc.core.claims.ImplicitIDToken

validate(now=None, leeway=0)

Validate everything in claims payload.

validate_c_hash()

Code hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token’s JOSE Header. For instance, if the alg is HS512, hash the code value with SHA-512, then take the left-most 256 bits and base64url encode them. The c_hash value is a case sensitive string. If the ID Token is issued from the Authorization Endpoint with a code, which is the case for the response_type values code id_token and code id_token token, this is REQUIRED; otherwise, its inclusion is OPTIONAL.

class authlib.oidc.core.UserInfo

The standard claims of a UserInfo object. Defined per Section 5.1.

REGISTERED_CLAIMS = ['sub', 'name', 'given_name', 'family_name', 'middle_name', 'nickname', 'preferred_username', 'profile', 'picture', 'website', 'email', 'email_verified', 'gender', 'birthdate', 'zoneinfo', 'locale', 'phone_number', 'phone_number_verified', 'address', 'updated_at']

registered claims that UserInfo supports

Community

This section aims to make Authlib sustainable, on governance, code commits, issues and finance.

Funding

If you use Authlib and its related projects commercially we strongly encourage you to invest in its sustainable development by sponsorship.

We accept funding with paid license and sponsorship. With the funding, it will:

  • contribute to faster releases, more features, and higher quality software.

  • allow more time to be invested in the documentation, issues, and community support.

And you can also get benefits from us:

  1. access to some of our private repositories

  2. access to our private PyPI.

  3. join our security mail list.

Get more details on our sponsor tiers page at:

  1. GitHub sponsors: https://github.com/sponsors/lepture

  2. Patreon: https://www.patreon.com/lepture

Insiders

Insiders are people who have access to our private repositories, you can become an insider with:

  1. purchasing a paid license at https://authlib.org/plans

  2. Become a sponsor with tiers including “Access to our private repos” benefit

PyPI

We offer a private PyPI server to release early security fixes and features. You can find more details about this PyPI server at:

https://authlib.org/pypi

Goals

The following list of funding goals shows features and additional addons we are going to add.

Funding Goal: $500/month
  • done setup a private PyPI

  • todo A running demo of loginpass services

  • todo Starlette integration of loginpass

Funding Goal: $2000/month
  • todo A simple running demo of OIDC provider in Flask

When the demo is complete, source code of the demo will only be available to our insiders.

Funding Goal: $5000/month

In Authlib v2.0, we will start working on async provider integrations.

  • todo Starlette (FastAPI) OAuth 1.0 provider integration

  • todo Starlette (FastAPI) OAuth 2.0 provider integration

  • todo Starlette (FastAPI) OIDC provider integration

Funding Goal: $9000/month

In Authlib v3.0, we will add built-in support for SAML.

  • todo SAML 2.0 implementation

  • todo RFC7522 (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants

  • todo CBOR Object Signing and Encryption

  • todo A complex running demo of OIDC provider

Our Sponsors

Here is our current sponsors, we keep a full list of our sponsors in the Authors page.

Support

If you have questions or issues about Authlib, there are several options:

StackOverflow

If your question does not contain sensitive information, and it is a help request instead of bug reports, please ask a question on StackOverflow and use the tag authlib.

Twitter

If your StackOverflow question is not answered for a long time, please ping me at Twitter @authlib.

GitHub Issues

GitHub issues is used for but reporting and feature requests. Please don’t ask questions on GitHub issues.

Feature Requests

If you have feature requests, please comment on Features Checklist. If they are accepted, they will be listed in the post.

Commercial Support

I do provide commercial support on Authlib. If you need personal help or consulting on your project, please send an email with a brief of what you need to <me@lepture.com>, I’ll decide whether I can take the job.

Note that work must be Authlib related. Find more information on https://authlib.org/support#consulting-and-supports

Security Vulnerability

If you think you have found a potential security vulnerability in Authlib, please email <me@lepture.com> directly.

Warning

Do not file a public issue.

Please do not disclose this to anyone else. We will retrieve a CVE identifier if necessary and give you full credit under whatever name or alias you provide. We will only request an identifier when we have a fix and can publish it in a release.

The Process

Here is the process when we have received a security report:

  1. we will reply to you in 24 hours

  2. we will confirm it in 2 days, if we can’t reproduce it, we will send emails to you for more information

  3. we will fix the issue in 1 week after we confirm it. If we can’t fix it for the moment, we will let you know.

  4. we will push the source code to GitHub when it has been released in PyPI for 1 week.

  5. if necessary, we will retrieve a CVE after releasing to PyPI.

Previous CVEs

Note

No CVEs yet

Contribution

Thanks for reading this section, you have a good intention. Since you are interested in contributing to Authlib, there are few things you need to know:

  1. All contributions are welcome, as long as everyone involved is treated with respect.

  2. Your contribution may be rejected, but don’t despair. It’s just that this certain pull request doesn’t fit Authlib. Be brave for a next contribution.

  3. Some issues will be labeled as good first issue, if you’re new to Authlib, you may find these issues are a good start for contribution.

Bug Reports

It’s welcome for everyone to submit a bug report. However, before you submit a report on GitHub issues, please check the old issues both open and closed, to ensure that you are not submitting a duplicate issue.

Please don’t ask for help on GitHub issues. Ask them on StackOverflow.

Documentation Contributions

Documentation improvements are welcome, both on grammar and the sentences. I’m not a native English speaker, you may find errors in this documentation, don’t hesitated to submit an improvement.

Our documentation is generated with Sphinx. All documentation should be written in reStructuredText, if you are not familiar reStructuredText, please read its documentation.

We keep a soft limit of 79 characters wide in text files. Yet, if you have to exceed this limit, it’s OK, but no more than 110 characters.

Make a Pull Request

Thank you. Now that you have a fix for Authlib, please describe it clearly in your pull request. There are some requirements for a pull request to be accepted:

  • Follow PEP8 code style. You can use flake8 to check your code style.

  • Tests for the code changes are required.

  • Please add documentation for it, if it requires.

Note

By making a pull request, you consent that the copyright of your pull request source code belongs to Authlib’s author.

Become a Backer

Finance support is also welcome. A better finance can make Authlib Sustainable. Here I offer two options:

  1. Recurring Pledges

    Recurring pledges come with exclusive benefits, e.g. having your name listed in the Authlib GitHub repository, or have your company logo placed on this website.

  2. One Time Donation

    I accept one time donation via Stripe, Alipay and Wechat. Donate via

    Support Hsiaoming Yang

Awesome Articles and Projects

This awesome list contains awesome articles and talks on Authlib, and awesome projects built with Authlib.

If you have an awesome article on Authlib, please submit a pull request. Note, some links may be removed in the future, we keep ONLY the awesome ones.

Examples
OAuth Clients Demo

An official example on how to use OAuth clients with Authlib in Flask, Django, Starlette, and FastAPI.

OAuth 2.0 Provider

An official example on how to create an OAuth 2.0 server with Authlib.

OpenID Connect 1.0 Provider

An official example on how to create an OpenID Connect server with Authlib.

Projects

Open source projects that are using Authlib to create an OAuth server.

uData

Customizable and skinnable social platform dedicated to (open)data.

NMOS Authorisation Server

AMWA NMOS BCP-003-02 Authorisation Server

anchore engine

A service that analyzes docker images and applies user-defined acceptance policies to allow automated container image validation and certification.

Articles

Sustainable

A sustainable project is trustworthy to use in your production environment. To make this project sustainable, we need your help. Here are several options:

Community Contribute

To make Authlib sustainable, we need your contribution. There are many ways for you, some of them even don’t require code writing:

  1. File a bug report when you found one.

  2. Solve issues already there.

  3. Write a blog post on Authlib.

  4. Give a star on GitHub and spread Authlib to other people.

Commercial License

Authlib is licensed under BSD for open source projects. If you are running a business, consider to purchase a commercial license instead.

Find more information on https://authlib.org/support#commercial-license

Commercial Support

I do provide commercial support on Authlib. If you need personal help or consulting on your project, please send an email with a brief of what you need to <me@lepture.com>, I’ll decide whether I can take the job.

Note that work must be Authlib related. Find more information on https://authlib.org/support#consulting-and-supports

Authors

Authlib is written and maintained by Hsiaoming Yang.

Contributors

Here is the list of the main contributors:

  • Ber Zoidberg

  • Tom Christie

  • Grey Li

  • Pablo Marti

  • Mario Jimenez Carrasco

  • Bastian Venthur

  • Nuno Santos

And more on https://github.com/lepture/authlib/graphs/contributors

Sponsors

Become a sponsor via GitHub Sponsors or Patreon to support Authlib.

Here is a full list of our sponsors, including past sponsors:

Find out the benefits for sponsorship.

Backers

Become a backer GitHub Sponsors or via Patreon to support Authlib.

Here is a full list of our backers:

Authlib Licenses

Authlib offers two licenses, one is BSD for open source projects, one is a commercial license for closed source projects.

Open Source License

Copyright (c) 2019, Hsiaoming Yang

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  • Neither the name of the creator nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Commercial License

The content of the commercial license can be found in the repository in a file named COMMERCIAL-LICENSE. You can get a commercial license at:

https://authlib.org/plans

Get Updates

Stay tuned with Authlib, here is a history of Authlib changes.

Changelog

Here you can see the full list of changes between each Authlib release.

Version 1.0.1

Released on April 6, 2022

  • Fix authenticate_none method, via issue#438.

  • Allow to pass in alternative signing algorithm to RFC7523 authentication methods via PR#447.

  • Fix missing_token for Flask OAuth client, via issue#448.

  • Allow openid in any place of the scope, via issue#449.

  • Security fix for validating essential value on blank value in JWT, via issue#445.

Version 1.0.0

Released on Mar 15, 2022.

We have dropped support for Python 2 in this release. We have removed built-in SQLAlchemy integration.

OAuth Client Changes:

The whole framework client integrations have been restructured, if you are using the client properly, e.g. oauth.register(...), it would work as before.

OAuth Provider Changes:

In Flask OAuth 2.0 provider, we have removed the deprecated OAUTH2_JWT_XXX configuration, instead, developers should define .get_jwt_config on OpenID extensions and grant types.

SQLAlchemy integrations has been removed from Authlib. Developers should define the database by themselves.

JOSE Changes

  • JWS has been renamed to JsonWebSignature

  • JWE has been renamed to JsonWebEncryption

  • JWK has been renamed to JsonWebKey

  • JWT has been renamed to JsonWebToken

The “Key” model has been re-designed, checkout the JSON Web Key (JWK) for updates.

Added ES256K algorithm for JWS and JWT.

Breaking Changes: find how to solve the deprecate issues via https://git.io/JkY4f

Version 0.15.5

Released on Oct 18, 2021.

  • Make Authlib compatible with latest httpx

  • Make Authlib compatible with latest werkzeug

  • Allow customize RFC7523 alg value

Version 0.15.4

Released on Jul 17, 2021.

  • Security fix when JWT claims is None.

Version 0.15.3

Released on Jan 15, 2021.

  • Fixed .authorize_access_token for OAuth 1.0 services, via issue#308.

Version 0.15.2

Released on Oct 18, 2020.

  • Fixed HTTPX authentication bug, via issue#283.

Version 0.15.1

Released on Oct 14, 2020.

  • Backward compatible fix for using JWKs in JWT, via issue#280.

Version 0.15

Released on Oct 10, 2020.

This is the last release before v1.0. In this release, we added more RFCs implementations and did some refactors for JOSE:

  • RFC8037: CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE)

  • RFC7638: JSON Web Key (JWK) Thumbprint

We also fixed bugs for integrations:

  • Fixed support for HTTPX>=0.14.3

  • Added OAuth clients of HTTPX back via PR#270

  • Fixed parallel token refreshes for HTTPX async OAuth 2 client

  • Raise OAuthError when callback contains errors via issue#275

Breaking Change:

1. The parameter algorithms in JsonWebSignature and JsonWebEncryption are changed. Usually you don’t have to care about it since you won’t use it directly. 2. Whole JSON Web Key is refactored, please check JSON Web Key (JWK).

Version 0.14.3

Released on May 18, 2020.

  • Fix HTTPX integration via PR#232 and PR#233.

  • Add “bearer” as default token type for OAuth 2 Client.

  • JWS and JWE don’t validate private headers by default.

  • Remove none auth method for authorization code by default.

  • Allow usage of user provided code_verifier via issue#216.

  • Add introspect_token method on OAuth 2 Client via issue#224.

Version 0.14.2

Released on May 6, 2020.

  • Fix OAuth 1.0 client for starlette.

  • Allow leeway option in client parse ID token via PR#228.

  • Fix OAuthToken when expires_at or expires_in is 0 via PR#227.

  • Fix auto refresh token logic.

  • Load server metadata before request.

Version 0.14.1

Released on Feb 12, 2020.

  • Quick fix for legacy imports of Flask and Django clients

Version 0.14

Released on Feb 11, 2020.

In this release, Authlib has introduced a new way to write framework integrations for clients.

Bug fixes and enhancements in this release:

  • Fix HTTPX integrations due to HTTPX breaking changes

  • Fix ES algorithms for JWS

  • Allow user given nonce via issue#180.

  • Fix OAuth errors get_headers leak.

  • Fix code_verifier via issue#165.

Breaking Change: drop sync OAuth clients of HTTPX.

Old Versions

Find old changelog at https://github.com/lepture/authlib/releases

  • Version 0.13.0: Released on Nov 11, 2019

  • Version 0.12.0: Released on Sep 3, 2019

  • Version 0.11.0: Released on Apr 6, 2019

  • Version 0.10.0: Released on Oct 12, 2018

  • Version 0.9.0: Released on Aug 12, 2018

  • Version 0.8.0: Released on Jun 17, 2018

  • Version 0.7.0: Released on Apr 28, 2018

  • Version 0.6.0: Released on Mar 20, 2018

  • Version 0.5.1: Released on Feb 11, 2018

  • Version 0.5.0: Released on Feb 11, 2018

  • Version 0.4.1: Released on Feb 2, 2018

  • Version 0.4.0: Released on Jan 31, 2018

  • Version 0.3.0: Released on Dec 24, 2017

  • Version 0.2.1: Released on Dec 6, 2017

  • Version 0.2.0: Released on Nov 25, 2017

  • Version 0.1.0: Released on Nov 18, 2017

Consider to follow Authlib on Twitter, and subscribe Authlib Blog.