Authlib: Python Authentication

Release v0.11. (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 Python2.7+ and Python3.5+.

Overview

A simple Flask OAuth Client which connects to the GitHub OAuth2 API:

from flask import Flask
from authlib.flask.client import OAuth
# use loginpass to make OAuth connection simpler
from loginpass import create_flask_blueprint, GitHub

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

def handle_authorize(remote, token, user_info):
    if token:
        save_token(remote.name, token)
    if user_info:
        save_user(user_info)
        return user_page
    raise some_error

github_bp = create_flask_blueprint(GitHub, oauth, handle_authorize)
app.register_blueprint(github_bp, url_prefix='/github')

OAuth server (provider) on the other hand is a little complex, find a real Flask OAuth 2.0 Server via Example of OAuth 2.0 server.

User Guide

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

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.

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.

Flexible

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. Flexible 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 flexible, 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 of Authlib

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:

  • requests
  • cryptography

Note

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

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 .

Understand OAuth 1.0

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 redirections.

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

OAuth 1.0 Flow

It takes more steps to obtain an access token than OAuth 2.0.

Roles in OAuth 1.0

There are usually three roles in an OAuth 1.0 flow. Let’s take Twitter as an example, you are building a mobile app to send tweets:

  • Client: a client is a third-party application. In this case, it is your 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.
Credentials

During the OAuth 1.0 process, there are several credentials passed from server to client, and vice versa.

  1. client credentials
  2. temporary credentials
  3. token credentials
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 view 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.

Signature

In OAuth 1.0, every request client sending to server requires a signature. The signature is calculated from:

  1. credentials (client, temporary, token)
  2. timestamp & nonce
  3. other HTTP information

Understand 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:

Client Guide

This part of the documentation contains information on the client parts. For Requests.Session, Flask integration and Django integration.

Here is a simple overview of Flask OAuth client:

from flask import Flask, jsonify
from authlib.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')
    return jsonify(profile)

Follow the documentation below to find out more in detail.

OAuth 1 Client

OAuth1Session for requests

The OAuth1Session in Authlib is a subclass of requests.Session. It shares the same API with requests.Session and extends it with OAuth 1 protocol. This section is a guide on how to obtain an access token in OAuth 1 flow.

Note

If you are using Flask or Django, you may have interests in Flask OAuth Client and Django OAuth Client.

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

There are three steps in OAuth 1 to obtain an access token. Initialize the session for reuse:

>>> from authlib.client import OAuth1Session
>>> client_id = 'Your Twitter client key'
>>> client_secret = 'Your Twitter client secret'
>>> session = OAuth1Session(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 = session.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:

>>> session.redirect_uri = 'https://your-domain.org/auth'
>>> session.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'
>>> session.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:

>>> session.create_authorization_url(authenticate_url)

Now visit the authorization url that OAuth1Session.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'
>>> session.parse_authorization_response(resp_url)
>>> access_token_url = 'https://api.twitter.com/oauth/access_token'
>>> token = session.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']
>>> session = 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 = session.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 = session.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']
>>> session = 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 = session.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:

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

Pass this auth to ``requests` to access protected resources:

import requests

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

New in version v0.11: This is an experimental feature.

The AsyncOAuth1Client is located in authlib.client.aiohttp. Authlib doesn’t embed aiohttp as a dependency, you need to install it yourself.

Here is an example on how you can initialize an instance of AsyncOAuth1Client for aiohttp:

import asyncio
from aiohttp import ClientSession
from authlib.client.aiohttp import AsyncOAuth1Client, OAuthRequest

REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'

async def main():
    # OAuthRequest is required to handle auth
    async with ClientSession(request_class=OAuthRequest) as session:
        client = AsyncOAuth1Client(session, 'client_id', 'client_secret', ...)
        token = await client.fetch_request_token(REQUEST_TOKEN_URL)
        print(token)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

The API is similar with OAuth1Session above. Using the client for the three steps authorization:

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 = await 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).

Redirect to Authorization Endpoint

The second step is to generate the authorization URL:

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

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

url = client.create_authorization_url(authenticate_url)
print(url)
'https://api.twitter.com/oauth/authenticate?oauth_token=gA..H'
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

In the production flow, you may need to create a new instance of AsyncOAuth1Client, it is the same as above. You need to use the previous request token to exchange an access token:

# twitter redirected back to your website
resp_url = 'https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq'

# you may use the ``oauth_token`` in resp_url to
# get back your previous request token
request_token = {'oauth_token': 'gA..H', 'oauth_token_secret': '...'}

# assign request token to client
client.token = request_token

# resolve the ``oauth_verifier`` from resp_url
oauth_verifier = get_oauth_verifier_value(resp_url)

access_token_url = 'https://api.twitter.com/oauth/access_token'
token = await client.fetch_access_token(access_token_url, oauth_verifier)

You can save the token to access protected resources later.

Access Protected Resources

Now you can access the protected resources. Usually, you will need to create an instance of AsyncOAuth1Client:

# get back the access token if you have saved it in some place
access_token = {'oauth_token': '...', 'oauth_secret': '...'}

# assign it to client
client.token = access_token

account_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
async with client.get(account_url) as resp:
    data = await resp.json()

Notice, it is also possible to create the client instance with access token at the initialization:

client = AsyncOAuth1Client(
    session, 'client_id', 'client_secret',
    token='...', token_secret='...',
    ...
)

Warning

This is still an experimental feature in Authlib. Use it with caution.

OAuth 2 Session

The OAuth2Session in Authlib was designed to be compatible with the one in requests-oauthlib. But now, there are some differences. This section is a guide on how to obtain an access token in OAuth 2 flow.

Note

This OAuth2Session is a customized requests.Session. It shares the same API with requests. If you are using Flask, you may have interests in Flask OAuth Client. If you are using Django, please read Django OAuth Client.

If you are not familiar with OAuth 2.0, it is better to Understand 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:

>>> from authlib.client import OAuth2Session
>>> client_id = 'Your GitHub client ID'
>>> client_secret = 'Your GitHub client secret'
>>> scope = 'user:email'  # we want to fetch user's email
>>> session = OAuth2Session(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:

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

The OAuth2Session.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 Access 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 OAuth2Session.fetch_access_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'
>>> access_token_url = 'https://github.com/login/oauth/access_token'
>>> token = session.fetch_access_token(access_token_url, 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()
>>> session = OAuth2Session(client_id, client_secret, state=state)
>>> session.fetch_access_token(access_token_url, authorization_response=authorization_response)

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

OAuth2Session for Implicit

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

>>> uri, state = session.create_authorization_url(authorize_url, 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 authoirzation 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 OAuth2Session.fetch_access_token():

>>> token = session.fetch_access_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 = session.fetch_access_token(token_url, 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 = session.fetch_access_token(token_url)
>>> # or with grant_type
>>> token = session.fetch_access_token(token_url, grant_type='client_credentials')
Client Authentication

When fetching access token, the authorization server will require a client authentication, Authlib has provided a OAuth2ClientAuth which supports 3 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:

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

If the authorization server requires other means of authentication, you can construct an auth of requests, and pass it to fetch_access_token:

>>> auth = YourAuth(...)
>>> token = session.fetch_access_token(token_url, 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 for Client Authentication.

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 = session.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:

>>> token = restore_access_token_from_database()
>>> # token is a dict which must contain ``access_token``, ``token_type``
>>> session = OAuth2Session(client_id, client_secret, token=token)
>>> account_url = 'https://api.github.com/user'
>>> resp = session.get(account_url)
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, linkedin is using a oauth2_access_token parameter in query string to protect users’ resources, let’s fix it:

from authlib.common.urls import add_params_to_uri

def _non_compliant_param_name(url, headers, data):
    access_token = session.token.get('access_token')
    token = [('oauth2_access_token', access_token)]
    url = add_params_to_uri(url, token)
    return url, headers, data

session.register_compliance_hook(
    'protected_request', _non_compliant_param_name)

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:

>>> from authlib.client import OAuth2Session
>>> client_id = 'Your Google client ID'
>>> client_secret = 'Your Google client secret'
>>> scope = 'openid email profile'
>>> session = OAuth2Session(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 thise 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()
>>> session.create_authorization_url(url, redirect_uri='xxx', nonce=nonce, ...)

At the last step of session.fetch_access_token, the return value contains a id_token:

>>> resp = session.fetch_access_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 JWT 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.client import AssertionSession

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

token_url = 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(
    grant_type=cls.JWT_BEARER_GRANT_TYPE,
    token_url=token_url,
    issuer=conf['client_email'],
    audience=token_url,
    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:

Flask OAuth Client

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. Here is how to Migrate OAuth Client from Flask-OAuthlib to Authlib.

Create a registry with OAuth object:

from authlib.flask.client import OAuth

oauth = OAuth(app)

You can initialize it later with init_app() method:

oauth = OAuth()
oauth.init_app(app)
Configuration

To register a remote application on OAuth registry, using the register() method:

oauth.register(
    name='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,
)

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

oauth.twitter.get('account/verify_credentials.json')

The second parameter in register method is configuration. Every key value pair can be omit. They can be configured in your Flask App configuration. Config key is formatted with {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 key would look like:

EXAMPLE_CLIENT_ID Twitter Consumer Key
EXAMPLE_CLIENT_SECRET Twitter Consumer Secret
EXAMPLE_ACCESS_TOKEN_URL URL to fetch OAuth access token

The remote app that OAuth.register() configured, is a subclass of OAuthClient. You can read more on OAuthClient. There are hooks for OAuthClient, and flask integration has registered them all for you.

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}_REFRESH_TOKEN_URL: Refresh Token endpoint for OAuth 2 (if any)
  • {name}_REFRESH_TOKEN_PARAMS: Extra parameters for Refresh 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
CLIENT_KWARGS

The {name}_CLIENT_KWARGS is a dict configuration to pass extra parameters to OAuth1Session or OAuth2Session.

For OAuth 1.0, you can pass extra parameters like:

EXAMPLE_CLIENT_KWARGS = {
    'signature_method': 'HMAC-SHA1',
    'signature_type': 'HEADER',
    'rsa_key': 'Your-RSA-Key'
}

For OAuth 2.0, you can pass extra parameters like:

EXAMPLE_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.

Database

Note

If OAuth login is what you need ONLY, you don’t need to configure a database with fetch_token method.

We need to fetch_token from database for later requests. Here is an example on database schema design:

class OAuth1Token(db.Model)
    user_id = Column(Integer, nullable=False)
    name = Column(String(20), nullable=False)

    oauth_token = Column(String(48), nullable=False)
    oauth_token_secret = Column(String(48))

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

class OAuth2Token(db.Model):
    user_id = Column(Integer, nullable=False)
    name = Column(String(20), nullable=False)

    token_type = Column(String(20))
    access_token = Column(String(48), nullable=False)
    refresh_token = Column(String(48))
    expires_at = Column(Integer, default=0)

    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,
        )

To send requests on behalf of the user, you need to save user’s access token into database after authorize_access_token. Then use the access token with fetch_token from database.

OAuth 1 Request Token

OAuth 1 requires a temporary request token for exchanging access token. There should be a place to store these temporary information. If a cache system is available, the ONLY thing you need to do is pass the cache instance into OAuth registry. A cache interface MUST have methods:

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

Pass the cache instance into OAuth registry:

from authlib.flask.client import OAuth

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

If cache system is not available, you can define methods for retrieving and saving request token:

def save_request_token(token):
    save_request_token_to_someplace(current_user, token)

def fetch_request_token():
    return get_request_token_from_someplace(current_user)

# register the two methods
oauth.register('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,
    # NOTICE HERE
    save_request_token=save_request_token,
    fetch_request_token=fetch_request_token,
)

Note

There is no “request token” in OAuth 2.0, you don’t need to implement this section if your are working on OAuth 2.0 integrations.

Flask OAuth Clients Routes

Let’s take Twitter as an example, we need to define routes for login and authorization:

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()
    # this is a pseudo method, you need to implement it yourself
    OAuth1Token.save(current_user, token)
    return redirect(url_for('twitter_profile'))

@app.route('/profile')
def twitter_profile():
    resp = oauth.twitter.get('account/verify_credentials.json')
    profile = resp.json()
    return render_template('profile.html', profile=profile)

There will be an issue with /profile since you our registry don’t know current user’s Twitter access token. We need to design a fetch_token, and grant it to the registry:

def fetch_twitter_token():
    item = OAuth1Token.query.filter_by(
        name='twitter', user_id=current_user.id
    ).first()
    return item.to_token()

# we can registry this ``fetch_token`` with oauth.register
oauth.register(
    '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,
    # NOTICE HERE
    fetch_token=fetch_twitter_token,
)

Since the OAuth registry can contain many services, it would be good enough to share some common methods instead of defining them one by one. Here are some hints:

from flask import url_for, render_template

@app.route('/login/<name>')
def login(name):
    client = oauth.create_client(name)
    redirect_uri = url_for('authorize', name=name, _external=True)
    return client.authorize_redirect(redirect_uri)

@app.route('/authorize/<name>')
def authorize(name):
    client = oauth.create_client(name)
    token = client.authorize_access_token()
    if name in OAUTH1_SERVICES:
        # this is a pseudo method, you need to implement it yourself
        OAuth1Token.save(current_user, token)
    else:
        # this is a pseudo method, you need to implement it yourself
        OAuth2Token.save(current_user, token)
    return redirect(url_for('profile', name=name))

@app.route('/profile/<name>')
def profile(name):
    client = oauth.create_client(name)
    resp = oauth.twitter.get(get_profile_url(name))
    profile = resp.json()
    return render_template('profile.html', profile=profile)

We can share a fetch_token method at OAuth registry level when initialization. Define a common fetch_token:

def fetch_token(name):
    if name in OAUTH1_SERVICES:
        item = OAuth1Token.query.filter_by(
            name=name, user_id=current_user.id
        ).first()
    else:
        item = OAuth2Token.query.filter_by(
            name=name, user_id=current_user.id
        ).first()
    if item:
        return item.to_token()

# pass ``fetch_token``
oauth = OAuth(app, fetch_token=fetch_token)

# or init app later
oauth = OAuth(fetch_token=fetch_token)
oauth.init_app(app)

# or init everything later
oauth = OAuth()
oauth.init_app(app, fetch_token=fetch_token)

With this common fetch_token in OAuth, you don’t need to design the method for each services one by one.

Auto Refresh Token

In OAuth 2, there is a concept of refresh_token, Authlib can auto refresh access token when it is expired. If the services you are using don’t issue any refresh_token at all, you don’t need to do anything.

Just like fetch_token, we can define a update_token method for each remote app or sharing it in OAuth registry:

def update_token(name, token):
    item = OAuth2Token.query.filter_by(
        name=name, user_id=current_user.id
    ).first()
    if not item:
        item = OAuth2Token(name=name, user_id=current_user.id)
    item.token_type = token.get('token_type', 'bearer')
    item.access_token = token.get('access_token')
    item.refresh_token = token.get('refresh_token')
    item.expires_at = token.get('expires_at')
    db.session.add(item)
    db.session.commit()
    return item

# pass ``update_token``
oauth = OAuth(app, update_token=update_token)

# or init app later
oauth = OAuth(update_token=update_token)
oauth.init_app(app)

# or init everything later
oauth = OAuth()
oauth.init_app(app, update_token=update_token)
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:

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=None,
    code_challenge_method='S256',
)

Note, the only supported code_challenge_method is S256.

Compliance Fix

The RemoteApp is a subclass of OAuthClient, they share the same logic for compliance fix. Construct a method to fix requests session:

def slack_compliance_fix(session):
    def _fix(resp):
        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)

When OAuth.register() a remote app, pass it in the 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.

Loginpass

There are many built-in integrations served by loginpass, checkout the flask_example in loginpass project. Here is an example of GitHub:

from flask import Flask
from authlib.flask.client import OAuth
from loginpass import create_flask_blueprint, GitHub

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

def handle_authorize(remote, token, user_info):
    if token:
        save_token(remote.name, token)
    if user_info:
        save_user(user_info)
        return user_page
    raise some_error

github_bp = create_flask_blueprint(GitHub, oauth, handle_authorize)
app.register_blueprint(github_bp, url_prefix='/github')
# Now, there are: ``/github/login`` and ``/github/auth``

The source code of loginpass is very simple, they are just preconfigured services integrations.

Django OAuth Client

Looking for OAuth providers?

The Django client shares a similar API with Flask client. But there are differences, since Django has no request context, you need to pass request argument yourself.

Create a registry with OAuth object:

from authlib.django.client import OAuth

oauth = OAuth()
Configuration

To register a remote application on OAuth registry, using the register() method:

oauth.register(
    '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,
)

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

oauth.twitter.get('account/verify_credentials.json')

The second parameter in register method is configuration. Every key value pair can be omit. 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
    }
}
client_kwargs

The client_kwargs is a dict configuration to pass extra parameters to OAuth1Session or OAuth2Session.

For OAuth 1.0, you can pass extra parameters like:

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

For OAuth 2.0, 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.

Sessions Middleware

In OAuth 1, Django client will save the request token in sessions. In this case, you 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.

Database Design

Authlib Django client has no built-in database model. You need to design the Token model by yourself. This is designed by intention.

Here are some hints on how to design your schema:

class OAuth1Token(models.Model):
    name = models.CharField(max_length=40)
    oauth_token = models.CharField(max_length=200)
    oauth_token_secret = models.CharField(max_length=200)
    # ...

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

class OAuth2Token(models.Model):
    name = models.CharField(max_length=40)
    token_type = models.CharField(max_length=20)
    access_token = models.CharField(max_length=200)
    refresh_token = models.CharField(max_length=200)
    # oauth 2 expires time
    expires_at = models.DateTimeField()
    # ...

    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,
        )
Implement the Server

There are two views to be completed, no matter it is OAuth 1 or OAuth 2:

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

def authorize(request):
    token = oauth.twitter.authorize_access_token(request)
    # save_token_to_db(token)
    return '...'

def fetch_resource(request):
    token = get_user_token_from_db(request.user)
    # remember to assign user's token to the client
    resp = oauth.twitter.get('account/verify_credentials.json', token=token)
    profile = resp.json()
    # ...

New in version v0.10.

When using the oauth client to make HTTP requests, developers will always need to get the token and pass the token into the requests. Here is an improved way to handle this issue with fetch_token feature:

def fetch_twitter_token(request):
    item = OAuth1Token.objects.get(
        name='twitter',
        user=request.user
    )
    return item.to_token()

# we can registry this ``fetch_token`` with oauth.register
oauth.register(
    '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,
    # NOTICE HERE
    fetch_token=fetch_twitter_token,
)

Developers can also pass the fetch_token to OAuth registry so that they don’t have to pass a fetch_token for each remote app. In this case, the fetch_token will accept two parameters:

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

    item = model.objects.get(
        name=name,
        user=request.user
    )
    return item.to_token()

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 fetch_resource(request):
    resp = oauth.twitter.get('account/verify_credentials.json', request=request)
    profile = resp.json()
    # ...
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:

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=None,
    code_challenge_method='S256',
)

Note, the only supportted code_challenge_method is S256.

Compliance Fix

The RemoteApp is a subclass of OAuthClient, they share the same logic for compliance fix. Construct a method to fix requests session:

def slack_compliance_fix(session):
    def _fix(resp):
        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)

When OAuth.register() a remote app, pass it in the 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.

OAuthClient

Important

This OAuthClient is designed for developing framework integrations. You are not supposed to use it directly, check out Flask OAuth Client and Django OAuth Client instead.

A mixed OAuth 1 and OAuth 2 client, one to control them both. With OAuthClient, we make the authorization much similar. It is the base class for framework integrations.

OAuthClient will automatically detect whether it is OAuth 1 or OAuth 2 via its parameters. OAuth 1 has request_token_url, while OAuth 2 doesn’t.

To use OAuthClient for requesting user resources, you need to subclass it, and implement a OAuthClient.get_token() method:

class MyOAuthClient(OAuthClient):
    def get_token(self):
        return get_current_user_token()
OAuth 1 Flow

Configure an OAuth 1 client with OAuthClient:

client = OAuthClient(
    client_id='Twitter Consumer Key',
    client_secret='Twitter Consumer Secret',
    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/',
)

There are other options that you could pass to the class. Please read the API documentation.

Redirect to Authorization Endpoint

There is a request token exchange in OAuth 1, in this case, we need to save the request token before heading over to authorization endpoint:

def save_request_token(token):
    session['token'] = token

# The first ``redirect_uri`` parameter is optional.
url, state = client.generate_authorize_redirect(
    save_request_token=save_request_token)

Now we will get a redirect url to the authorization endpoint. The return value is a tuple of (url, state), in OAuth 1, state will always be None.

Get Access Token

If permission is granted, we can fetch the access token now:

def get_request_token():
    return session.pop('token', None)

redirect_uri = session.pop('redirect_uri', None)
params = parse_response_url_qs()
token = client.fetch_access_token(
    redirect_uri, get_request_token=get_request_token, **params)
save_token_to_db(token)
OAuth 2 Flow

The flow of OAuth 2 is similar with OAuth 1, and much simpler:

client = OAuthClient(
    client_id='GitHub Client ID',
    client_secret='GitHub Client Secret',
    api_base_url='https://api.github.com/',
    access_token_url='https://github.com/login/oauth/access_token',
    authorize_url='https://github.com/login/oauth/authorize',
    client_kwargs={'scope': 'user:email'},
)
Redirect to Authorization Endpoint

Unlike OAuth 1, there is no request token. The process to authorization server is very simple:

redirect_uri = 'https://example.com/auth'
url, state = client.generate_authorize_redirect(redirect_uri)
# save state for getting access token
session['state'] = state

Note that, in OAuth 2, there will be a state always, you need to save it for later use.

Get Access Token

It’s the same as OAuth 1. If permission is granted, we can fetch the access token now:

redirect_uri = session.pop('redirect_uri', None)
params = parse_response_url_qs()
# you need to verify state here
assert params['state'] == session.pop('state')
token = client.fetch_access_token(redirect_uri, **params)
save_token_to_db(token)
Compliance Fix

Since many OAuth 2 providers are not following standard strictly, we need to fix them. It has been introduced in Compliance Fix for non Standard.

For OAuthClient, we can register our hooks one by one, with OAuth2Session.register_compliance_hook():

client.session.register_compliance_hook('protected_request', func)

However, there is a shortcut attribute for it. You need to construct a method which takes session as the parameter:

def compliance_fix(session):

    def fix_protected_request(url, headers, data):
        # do something
        return url, headers, data

    def fix_access_token_response(response):
        # patch response
        return response

    session.register_compliance_hook(
        'protected_request', fix_protected_request)
    session.register_compliance_hook(
        'access_token_response', fix_access_token_response)
    # register other hooks

Later, when you initialized OAuthClient, pass it to the client parameters:

client = OAuthClient(
    client_id='...',
    client_secret='...',
    ...,
    compliance_fix=compliance_fix,
    ...
)

It will automatically patch the requests session for OAuth 2.

Client API References

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

Sessions and Client
class authlib.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.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.client.OAuth2Session(client_id=None, client_secret=None, token_endpoint_auth_method=None, refresh_token_url=None, refresh_token_params=None, scope=None, redirect_uri=None, token=None, token_placement='header', state=None, token_updater=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.
  • token_endpoint_auth_method – Client auth method for token endpoint.
  • refresh_token_url – Refresh Token endpoint for auto refresh token.
  • refresh_token_params – Extra parameters for refresh token endpoint.
  • scope – Scope that you needed to access user resources.
  • 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”.
  • state – State string used to prevent CSRF. This will be given when creating the authorization url and must be supplied when parsing the authorization response.
  • token_updater – A function for you to update token. It accept a OAuth2Token as parameter.
create_authorization_url(url, state=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.
  • kwargs – Extra parameters to include.
Returns:

authorization_url, state

fetch_access_token(url=None, **kwargs)

Alias for fetch_token.

fetch_token(url=None, code=None, authorization_response=None, body='', auth=None, username=None, password=None, method='POST', headers=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).
  • code – Authorization code (if any)
  • authorization_response – Authorization response URL, the callback URL of the request back to you. We can extract authorization code from it.
  • 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.
  • username – Username of the resource owner for password grant.
  • password – Password of the resource owner for password grant.
  • 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.
Returns:

A OAuth2Token object (a dict too).

refresh_token(url=None, 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(func)

Extend client authenticate for token endpoint.

Parameters:func – a function 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_response: invoked before refresh token parsing.
  • protected_request: invoked before making a request.
  • revoke_token_request: invoked before revoking a token.
revoke_token(url, token, 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:

A OAuth2Token object (a dict too).

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

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

refresh_token()

Using Assertions as Authorization Grants to refresh token as described in Section 4.1.

request(method, url, data=None, headers=None, withhold_token=False, auth=None, **kwargs)

Send request with auto refresh token feature.

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

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

class authlib.client.OAuthClient(client_id=None, client_secret=None, request_token_url=None, request_token_params=None, access_token_url=None, access_token_params=None, refresh_token_url=None, refresh_token_params=None, authorize_url=None, authorize_params=None, api_base_url=None, client_kwargs=None, server_metadata_url=None, compliance_fix=None, **kwargs)

A mixed OAuth client for OAuth 1 and OAuth 2.

Parameters:
  • client_id – Client key of OAuth 1, or Client ID of OAuth 2
  • client_secret – Client secret of OAuth 2, or Client Secret of OAuth 2
  • request_token_url – Request Token endpoint for OAuth 1
  • request_token_params – Extra parameters for Request Token endpoint
  • access_token_url – Access Token endpoint for OAuth 1 and OAuth 2
  • access_token_params – Extra parameters for Access Token endpoint
  • refresh_token_url – Refresh Token endpoint for OAuth 2 (if any)
  • refresh_token_params – Extra parameters for Refresh Token endpoint
  • authorize_url – Endpoint for user authorization of OAuth 1 ro OAuth 2
  • authorize_params – Extra parameters for Authorization Endpoint
  • api_base_url – The base API endpoint to make requests simple
  • client_kwargs – Extra keyword arguments for session
  • server_metadata_url – Discover server metadata from this URL
  • kwargs – Extra keyword arguments

Create an instance of OAuthClient. If request_token_url is configured, it would be an OAuth 1 instance, otherwise it is OAuth 2 instance:

oauth1_client = OAuthClient(
    client_id='Twitter Consumer Key',
    client_secret='Twitter Consumer Secret',
    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/',
)

oauth2_client = OAuthClient(
    client_id='GitHub Client ID',
    client_secret='GitHub Client Secret',
    api_base_url='https://api.github.com/',
    access_token_url='https://github.com/login/oauth/access_token',
    authorize_url='https://github.com/login/oauth/authorize',
    client_kwargs={'scope': 'user:email'},
)
generate_authorize_redirect(redirect_uri=None, save_request_token=None, **kwargs)

Generate the authorization url and state for HTTP redirect.

Parameters:
  • redirect_uri – Callback or redirect URI for authorization.
  • save_request_token – A function to save request token.
  • kwargs – Extra parameters to include.
Returns:

(url, state)

fetch_access_token(redirect_uri=None, request_token=None, **params)

Fetch access token in one step.

Parameters:
  • redirect_uri – Callback or Redirect URI that is used in previous authorize_redirect().
  • request_token – A previous request token for OAuth 1.
  • params – Extra parameters to fetch access token.
Returns:

A token dict.

get(url, **kwargs)

Invoke GET http request.

If api_base_url configured, shortcut is available:

client.get('users/lepture')
post(url, **kwargs)

Invoke POST http request.

If api_base_url configured, shortcut is available:

client.post('timeline', json={'text': 'Hi'})
patch(url, **kwargs)

Invoke PATCH http request.

If api_base_url configured, shortcut is available:

client.patch('profile', json={'name': 'Hsiaoming Yang'})
put(url, **kwargs)

Invoke PUT http request.

If api_base_url configured, shortcut is available:

client.put('profile', json={'name': 'Hsiaoming Yang'})
delete(url, **kwargs)

Invoke DELETE http request.

If api_base_url configured, shortcut is available:

client.delete('posts/123')
Flask Registry and RemoteApp
class authlib.flask.client.OAuth(app=None, cache=None, fetch_token=None, update_token=None)

Registry for oauth clients.

Parameters:app – the app instance of Flask

Create an instance with Flask:

oauth = OAuth(app, cache=cache)

You can also pass the instance of Flask later:

oauth = OAuth()
oauth.init_app(app, cache=cache)
Parameters:
  • app – Flask application instance
  • cache – A cache instance that has .get .set and .delete methods
  • fetch_token – a shared function to get current user’s token
  • update_token – a share function to update current user’s token
init_app(app, cache=None, fetch_token=None, update_token=None)

Init app with Flask instance.

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

Registers a new remote application.

Parameters:
  • name – Name of the remote application.
  • overwrite – Overwrite existing config with Flask config.
  • kwargs – Parameters for RemoteApp.

Find parameters from OAuthClient. When a remote app is registered, it can be accessed with named attribute:

oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
class authlib.flask.client.RemoteApp(name, fetch_token=None, update_token=None, fetch_request_token=None, save_request_token=None, **kwargs)

Flask integrated RemoteApp of OAuthClient. It has built-in hooks for OAuthClient. The only required configuration is token model.

authorize_access_token(**kwargs)

Authorize access token.

authorize_redirect(redirect_uri=None, **kwargs)

Create a HTTP Redirect for Authorization Endpoint.

Parameters:
  • redirect_uri – Callback or redirect URI for authorization.
  • kwargs – Extra parameters to include.
Returns:

A HTTP redirect response.

save_authorize_state(redirect_uri=None, state=None)

Save redirect_uri and state into session during authorize step.

Django Registry and RemoteApp
class authlib.django.client.OAuth(fetch_token=None)

Registry for oauth clients.

Create an instance for registry:

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

Registers a new remote application.

Parameters:
  • name – Name of the remote application.
  • overwrite – Overwrite existing config with django settings.
  • kwargs – Parameters for RemoteApp.

Find parameters from OAuthClient. When a remote app is registered, it can be accessed with named attribute:

oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
class authlib.django.client.RemoteApp(name, fetch_token=None, **kwargs)

Django integrated RemoteApp of OAuthClient. It has built-in hooks for OAuthClient.

authorize_access_token(request, **kwargs)

Fetch access token in one step.

Parameters:request – HTTP request instance from Django view.
Returns:A token dict.
authorize_redirect(request, redirect_uri=None, **kwargs)

Create a HTTP Redirect for Authorization Endpoint.

Parameters:
  • request – HTTP request instance from Django view.
  • redirect_uri – Callback or redirect URI for authorization.
  • kwargs – Extra parameters to include.
Returns:

A HTTP redirect response.

save_authorize_state(request, redirect_uri=None, state=None)

Save redirect_uri and state into session during authorize step.

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 JWS.serialize_compact(), build a JWS instance with JWA:

from authlib.jose import JWS
from authlib.jose import JWS_ALGORITHMS

jws = JWS(algorithms=JWS_ALGORITHMS)
# 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
  4. PS256, PS384, PS512

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

jws = JWS(algorithms=JWS_ALGORITHMS)
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 JWS.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']

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

JWS.serialize_json() is used to generate a JWS JSON Serialization, JWS.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 JWS.serialize() and JWS.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

JWS 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 = JWS(algorithms=JWS_ALGORITHMS, 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 JWE.serialize_compact(), build a JWE instance with JWA:

from authlib.jose import JWE
from authlib.jose import JWE_ALGORITHMS

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

s = jws.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 = jws.serialize_compact(protected, payload, key)

To deserialize a JWE Compact Serialization, use JWE.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)

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. How do we dumps a key into JWK, and loads JWK back into key? The interface of JWK contains these two methods.

Algorithms for kty (Key Type) is defined by RFC7518: JSON Web Algorithms. Available kty values are: EC, RSA and oct. Initialize a JWK instance with JWA:

from authlib.jose import JWK
from authlib.jose import JWK_ALGORITHMS

jwk = JWK(algorithms=JWK_ALGORITHMS)
key = read_file('public.pem')
obj = jwk.dumps(key, kty='RSA')
# obj is a dict, you may turn it into JSON
key = jwk.loads(obj)

There is an jwk instance in authlib.jose, so that you don’t need to initialize JWK yourself, try:

from authlib.jose import jwk
key = read_file('public.pem')
obj = jwk.dumps(key, kty='RSA')
# obj is a dict, you may turn it into JSON
key = jwk.loads(obj)

You may pass extra parameters into dumps 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 :specs/rfc7516 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 JWT. It has all supported JWS algorithms, and it can handle JWK automatically. When JWT.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 Payload Claims Validation

JWT.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.

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

Here are the details in documentation:

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.

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)

Authlib has provided a mixin for SQLAlchemy, define the client with this mixin:

from authlib.flask.oauth1.sqla import OAuth1ClientMixin

class Client(db.Model, OAuth1ClientMixin):
    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 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 has 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, there is also a SQLAlchemy mixin:

from authlib.flask.oauth1.sqla import OAuth1TemporaryCredentialMixin

class TemporaryCredential(db.Model, OAuth1TemporaryCredentialMixin):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
    )
    user = db.relationship('User')

To make a Temporary Credentials model yourself, get more information with ClientMixin API reference.

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.

Here is a SQLAlchemy mixin for easy integration:

from authlib.flask.oauth1.sqla import OAuth1TokenCredentialMixin

class TokenCredential(db.Model, OAuth1TokenCredentialMixin):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
    )
    user = db.relationship('User')

def set_user_id(self, user_id):
    self.user_id = user_id

If SQLAlchemy is not what you want, read the API reference of TokenCredentialMixin and implement the missing methods.

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, there is also a SQLAlchemy mixin:

from authlib.flask.oauth1.sqla import OAuth1TokenCredentialMixin

class TimestampNonce(db.Model, OAuth1TokenCredentialMixin)
    id = db.Column(db.Integer, primary_key=True)
Define A Server

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

from authlib.flask.oauth1 import AuthorizationServer
from authlib.flask.oauth1.sqla import create_query_client_func

query_client = create_query_client_func(db.session, Client)
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.flask.oauth1.cache import (
    register_nonce_hooks,
    register_temporary_credential_hooks
)
from authlib.flask.oauth1.sqla import register_token_credential_hooks

register_nonce_hooks(server, cache)
register_temporary_credential_hooks(server, cache)
register_token_credential_hooks(server, db.session, TokenCredential)

If cache is not available, here are the helpers for SQLAlchemy:

from authlib.flask.oauth1.sqla import (
    register_nonce_hooks,
    register_temporary_credential_hooks,
    register_token_credential_hooks
)

register_nonce_hooks(server, db.session, TimestampNonce)
register_temporary_credential_hooks(server, db.session, TemporaryCredential)
register_token_credential_hooks(server, db.session, TokenCredential)
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_credential_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)
    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

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.flask.oauth1 import ResourceProtector, current_credential
from authlib.flask.oauth1.cache import create_exists_nonce_func
from authlib.flask.oauth1.sqla import (
    create_query_client_func,
    create_query_token_func
)

query_client = create_query_client_func(db.session, Client)
query_token = create_query_token_func(db.session, TokenCredential)
exists_nonce = create_exists_nonce_func(cache)
# OR: authlib.flask.oauth1.sqla.create_exists_nonce_func

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.

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.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.get_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.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 query_token method:

from authlib.flask.oauth1 import ResourceProtector, current_credential
from authlib.flask.oauth1.cache import create_exists_nonce_func
from authlib.flask.oauth1.sqla import (
    create_query_client_func,
    create_query_token_func,
)
from your_project.models import Token, User, cache

# you need to define a ``cache`` instance yourself

require_oauth= ResourceProtector(
    app,
    query_client=create_query_client_func(db.session, OAuth1Client),
    query_token=create_query_token_func(db.session, OAuth1Token),
    exists_nonce=create_exists_nonce_func(cache)
)
# or initialize it lazily
require_oauth = ResourceProtector()
require_oauth.init_app(
    app,
    query_client=create_query_client_func(db.session, OAuth1Client),
    query_token=create_query_token_func(db.session, OAuth1Token),
    exists_nonce=create_exists_nonce_func(cache)
)
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.flask.oauth1.current_credential

Routes protected by ResourceProtector can access current credential with this variable.

Cache Helper Functions
authlib.flask.oauth1.cache.create_exists_nonce_func(cache, key_prefix='nonce:', expires=86400)

Create an exists_nonce function that can be used in hooks and resource protector.

Parameters:
  • cache – Cache instance
  • key_prefix – key prefix for temporary credential
  • expires – Expire time for nonce
authlib.flask.oauth1.cache.register_nonce_hooks(authorization_server, cache, key_prefix='nonce:', expires=86400)

Register nonce related hooks to authorization server.

Parameters:
  • authorization_server – AuthorizationServer instance
  • cache – Cache instance
  • key_prefix – key prefix for temporary credential
  • expires – Expire time for nonce
authlib.flask.oauth1.cache.register_temporary_credential_hooks(authorization_server, cache, key_prefix='temporary_credential:')

Register temporary credential related hooks to authorization server.

Parameters:
  • authorization_server – AuthorizationServer instance
  • cache – Cache instance
  • key_prefix – key prefix for temporary credential
SQLAlchemy Help Functions
authlib.flask.oauth1.sqla.create_query_client_func(session, model_class)

Create an query_client function that can be used in authorization server and resource protector.

Parameters:
  • session – SQLAlchemy session
  • model_class – Client class
authlib.flask.oauth1.sqla.create_query_token_func(session, model_class)

Create an query_token function that can be used in resource protector.

Parameters:
  • session – SQLAlchemy session
  • model_class – TokenCredential class
authlib.flask.oauth1.sqla.create_exists_nonce_func(session, model_class)

Create an exists_nonce function that can be used in hooks and resource protector.

Parameters:
  • session – SQLAlchemy session
  • model_class – TimestampNonce class
authlib.flask.oauth1.sqla.register_nonce_hooks(authorization_server, session, model_class)

Register nonce related hooks to authorization server.

Parameters:
  • authorization_server – AuthorizationServer instance
  • session – SQLAlchemy session
  • model_class – TimestampNonce class
authlib.flask.oauth1.sqla.register_temporary_credential_hooks(authorization_server, session, model_class)

Register temporary credential related hooks to authorization server.

Parameters:
  • authorization_server – AuthorizationServer instance
  • session – SQLAlchemy session
  • model_class – TemporaryCredential class
authlib.flask.oauth1.sqla.register_token_credential_hooks(authorization_server, session, model_class)

Register token credential related hooks to authorization server.

Parameters:
  • authorization_server – AuthorizationServer instance
  • session – SQLAlchemy session
  • model_class – TokenCredential class

Flask OAuth 2.0 Server

This section is not a step by step guide on how to create an OAuth 2.0 server 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 (not ready)

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 OAuth 2 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.flask.oauth2.sqla 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 by now. MAC Token is still under drafts, 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.flask.oauth2.sqla 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.

Server

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

from authlib.flask.oauth2 import AuthorizationServer

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.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
    item = Token(
        client_id=request.client.client_id,
        user_id=user_id,
        **token
    )
    db.session.add(item)
    db.session.commit()

# or with the helper
from authlib.flask.oauth2.sqla 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.validate_consent_request(end_user=current_user)
        return render_template(
            'authorize.html',
            grant=grant,
            user=current_user,
        )
    confirmed = request.form['confirm']
    if confirmed:
        # granted by resource owner
        return server.create_authorization_response(current_user)
    # denied by resource owner
    return server.create_authorization_response(None)

This is a simple demo, the real case should be more complex. There is a demo in authlib/playground, get a real taste with Authlib Playground.

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 creating some documentation for errors. Here is a list of built-in Errors.

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 are 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 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 a SQLAlchemy mixin for AuthorizationCode:

from authlib.flask.oauth2.sqla 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 subclass AuthorizationCodeGrant:

from authlib.oauth2.rfc6749 import grants
from authlib.common.security import generate_token

class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
    def create_authorization_code(self, client, grant_user, request):
        # you can use other method to generate this code
        code = generate_token(48)
        item = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=grant_user.get_user_id(),
        )
        db.session.add(item)
        db.session.commit()
        return code

    def parse_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']
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

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 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):
        item = Token.query.filter_by(refresh_token=refresh_token).first()
        # define is_refresh_token_expired by yourself
        if item and not item.is_refresh_token_expired():
            return item

    def authenticate_user(self, credential):
        return User.query.get(credential.user_id)

# 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'
    ]
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 import grants


class MyCustomGrant(grants.BaseGrant):
    AUTHORIZATION_ENDPOINT = False  # if you want to support it
    TOKEN_ENDPOINT = True  # if you want to support it
    GRANT_TYPE = 'custom-grant-type-name'

    def validate_authorization_request(self):
        # only needed if AUTHORIZATION_ENDPOINT = True

    def create_authorization_response(self, grant_user):
        # only needed if AUTHORIZATION_ENDPOINT = True

    def validate_token_request(self):
        # only needed if TOKEN_ENDPOINT = True

    def create_token_response(self):
        # only needed if TOKEN_ENDPOINT = True

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.

Grant can accept extensions. Developers can pass extensions when registering grant:

authorization_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

Flask OAuth 2.0 authorization server has a method to register other token endpoints: authorization_server.register_endpoint. Find the available endpoints:

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:

from flask import jsonify
from authlib.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()

    def request_invalid(self, request):
        return False

    def token_revoked(self, token):
        return token.revoked

require_oauth = ResourceProtector()

# only bearer token is supported currently
require_oauth.register_token_validator(MyBearerTokenValidator())

# you can also create BearerTokenValidator with shortcut
from authlib.flask.oauth2.sqla import create_bearer_token_validator

BearerTokenValidator = create_bearer_token_validator(db.session, Token)
require_oauth.register_token_validator(BearerTokenValidator())

@app.route('/user')
@require_oauth('profile')
def user_profile():
    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 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

You can apply multiple scopes to one endpoint in AND and OR modes. The default is AND mode.

@app.route('/profile')
@require_oauth('profile email', 'AND')
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', 'OR')
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 pass a function as the scope operator. e.g.:

def scope_operator(token_scopes, resource_scopes):
    # this equals "AND"
    return token_scopes.issuperset(resource_scopes)

@app.route('/profile')
@require_oauth('profile email', scope_operator)
def user_profile():
    user = current_token.user
    return jsonify(user)
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 OpenID Connect 1.0

OpenID Connect 1.0 is supported from version 0.6. The integrations are built with Custom Grant Types. Since OpenID Connect is built on OAuth 2.0 frameworks, you need to read Flask OAuth 2.0 Server at first.

Configuration

OpenID Connect 1.0 requires JWT. It can be enabled by setting:

OAUTH2_JWT_ENABLED = True

When JWT is enabled, these configurations are available:

OAUTH2_JWT_ALG Algorithm for JWT
OAUTH2_JWT_KEY Private key (in text) for JWT
OAUTH2_JWT_KEY_PATH Private key path for JWT
OAUTH2_JWT_ISS Issuer value for JWT
OAUTH2_JWT_EXP JWT expires time, default is 3600
OAUTH2_JWT_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.

OAUTH2_JWT_KEY / OAUTH2_JWT_KEY_PATH

A private key is required to generate JWT. The value can be configured with either OAUTH2_JWT_KEY or OAUTH2_JWT_KEY_PATH. 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 a RSA private key. It can be set with:

OAUTH2_JWT_KEY = '''-----BEGIN RSA PRIVATE KEY-----\nMIIEog...'''

# or in JWK format
OAUTH2_JWT_KEY = {"kty": "RSA", "n": ...}

# or in JWK set format
OAUTH2_JWT_KEY = {"keys": [{"kty": "RSA", "kid": "uu-id", ...}, ...]}

If you are using JWK set format, that would be better. Authlib will randomly choose a key among them to sign the JWT. To make it easier for maintenance, OAUTH2_JWT_KEY_PATH is a good choice:

OAUTH2_JWT_KEY_PATH = '/path/to/rsa_private.pem'
OAUTH2_JWT_KEY_PATH = '/path/to/jwk_set_private.json'
OAUTH2_JWT_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 Code flow looks like the standard Authorization Code flow, and the implementation for OpenIDCodeGrant is actually a subclass of Authorization Code Grant. And the implementation is the same:

from authlib.oidc.core import grants
from authlib.common.security import generate_token

class OpenIDCodeGrant(grants.OpenIDCodeGrant):
    def create_authorization_code(self, client, grant_user, request):
        # you can use other method to generate this code
        code = generate_token(48)
        # openid request MAY have "nonce" parameter
        nonce = request.data.get('nonce')
        item = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            nonce=nonce,
            user_id=grant_user.get_user_id(),
        )
        db.session.add(item)
        db.session.commit()
        return code

    def parse_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(OpenIDCodeGrant)

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

OpenIDCodeGrant can handle the standard code flow too. You MUST NOT use them together.

Important

If the server can handle OpenID requests, use OpenIDCodeGrant. DON’T register_grant(AuthorizationCodeGrant).

Implicit Flow

Implicit flow is simple, there is no missing methods should be implemented, we can simply import it and register it:

from authlib.oidc.core import grants
server.register_grant(grants.OpenIDImplicitGrant)
Hybrid Flow

Hybrid flow is a mix of the code flow and implicit flow. The missing methods are the same with code flow:

from authlib.oidc.core import grants
from authlib.common.security import generate_token

class OpenIDHybridGrant(grants.OpenIDHybridGrant):
    def create_authorization_code(self, client, grant_user, request):
        # you can use other method to generate this code
        code = generate_token(48)
        # openid request MAY have "nonce" parameter
        nonce = request.data.get('nonce')
        item = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            nonce=nonce,
            user_id=grant_user.get_user_id(),
        )
        db.session.add(item)
        db.session.commit()
        return code

    def parse_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(OpenIDHybridGrant)

API References of Flask OAuth 2.0 Server

This part of the documentation covers the interface of Flask OAuth 2.0 Server.

class authlib.flask.oauth2.AuthorizationServer(app=None, query_client=None, save_token=None, **config)

Flask implementation of authlib.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.get_user_id()
    else:
        user_id = None
    client = request.client
    tok = Token(
        client_id=client.client_id,
        user_id=user.get_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 – OAuth2Request instance.
  • grant_user – if granted, it is resource owner. If denied, it is None.
Returns:

Response

create_bearer_token_generator(app)

Create a generator function for generating token value. This method will create a Bearer Token generator with authlib.oauth2.rfc6750.BearerToken. By default, it will not generate refresh_token, which can be turn on by configuration OAUTH2_REFRESH_TOKEN_GENERATOR=True.

create_endpoint_response(name, request=None)

Validate endpoint request and create endpoint response.

Parameters:
  • name – Endpoint name
  • request – OAuth2Request instance.
Returns:

Response

create_token_expires_in_generator(app)

Create a generator function for generating expires_in value. Developers can re-implement this method with a subclass if other means required. The default expires_in value is defined by grant_type, different grant_type has different value. It can be configured with:

OAUTH2_TOKEN_EXPIRES_IN = {
    'authorization_code': 864000,
    'urn:ietf:params:oauth:grant-type:jwt-bearer': 3600,
}
create_token_response(request=None)

Validate token request and create token response.

Parameters:request – OAuth2Request instance
register_endpoint(endpoint_cls)

Add token endpoint to authorization server. e.g. RevocationEndpoint:

authorization_server.register_endpoint(RevocationEndpoint)
Parameters:endpoint_cls – A token 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.

Validate current HTTP request for authorization page. This page is designed for resource owner to grant or deny the authorization:

@app.route('/authorize', methods=['GET'])
def authorize():
    try:
        grant = server.validate_consent_request(end_user=current_user)
        return render_template(
            'authorize.html',
            grant=grant,
            user=current_user
        )
    except OAuth2Error as error:
        return render_template(
            'error.html',
            error=error
        )
class authlib.flask.oauth2.ResourceProtector

A protecting method for resource servers. Creating a require_oauth decorator easily with ResourceProtector:

from authlib.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(scope=None, operator='AND')

A method to acquire current valid token with the given scope.

Parameters:
  • scope – string or list of scope values
  • operator – value of “AND” or “OR”
Returns:

token object

acquire(scope=None, operator='AND')

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.flask.oauth2.current_token

Routes protected by ResourceProtector can access current token with this variable:

from authlib.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
Cache Helper Functions
authlib.flask.oauth2.cache.register_cache_authorization_code(cache, authorization_server, authenticate_user)

Use cache for authorization code grant endpoint.

Parameters:
  • cache – Cache instance.
  • authorization_server – AuthorizationServer instance.
  • authenticate_user – A function to authenticate user.
SQLAlchemy Helper Functions
authlib.flask.oauth2.sqla.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.flask.oauth2.sqla.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.flask.oauth2.sqla.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.flask.oauth2.sqla.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.flask.oauth2.sqla.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 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

Here are the details in documentation:

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.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.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.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.get_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.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

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: not ready yet.
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.get_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 Understand OAuth 2.0 at first. Here are some tips:

  1. Have a better Understand 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
API References

Here are the API references for developers. For framework level interfaces, check:

Servers
class authlib.oauth2.rfc6749.AuthorizationServer(query_client, generate_token, save_token, **config)

Authorization server that handles Authorization Endpoint and Token Endpoint.

Parameters:
  • query_client – A function to get client by client_id. The client model class MUST implement the methods described by ClientMixin.
  • token_generator – A method to generate tokens.
create_authorization_response(request=None, grant_user=None)

Validate authorization request and create authorization response.

Parameters:
  • request – OAuth2Request 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 – OAuth2Request instance.
Returns:

Response

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 – OAuth2Request instance
get_authorization_grant(request)

Find the authorization grant for current request.

Parameters:request – OAuth2Request instance.
Returns:grant instance
get_token_grant(request)

Find the token grant for current request.

Parameters:request – OAuth2Request instance.
Returns:grant instance
get_translations(request)

Return a translations instance used for i18n error messages. Framework SHOULD implement this function.

handle_response(status, body, headers)

Return HTTP response. Framework MUST implement this function.

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 token endpoint to authorization server. e.g. RevocationEndpoint:

authorization_server.register_endpoint(RevocationEndpoint)
Parameters:endpoint_cls – A token 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.oauth2.rfc6749.ResourceProtector
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:

def check_client_secret(self, client_secret):
    return self.client_secret == client_secret
Parameters:client_secret – A string of client secret
Returns:bool
check_client_type(client_type)

Validate if the client is the given client_type. The available choices are:

  • public:
    Clients incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application), and incapable of secure client authentication via any other means.
  • confidential:
    Clients capable of maintaining the confidentiality of their credentials (e.g., client implemented on a secure server with restricted access to the client credentials), or capable of secure client authentication using other means.

Developers can overwrite this method to implement a new logic.

Parameters:client_type – string of “public” or “confidential”
Returns:bool
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_requested_scopes(scopes)

Validate if the request scopes are supported by this client. It can always be True. For instance, there is a scope column:

def check_requested_scopes(self, scopes):
    return set(self.scope.split()).issuperset(scopes)
Parameters:scopes – the requested scopes set.
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
check_token_endpoint_auth_method(method)

Check client token_endpoint_auth_method defined via RFC7591. 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
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
has_client_secret()

A method returns that if the client has client_secret value. If the value is in client_secret column:

def has_client_secret(self):
    return bool(self.client_secret)
Returns:bool
Errors
class authlib.oauth2.rfc6749.OAuth2Error(description=None, uri=None, status_code=None, state=None)
get_body()

Get a list of body.

class authlib.oauth2.rfc6749.InsecureTransportError(description=None, uri=None, status_code=None, state=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)

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)

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)

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)

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(description=None, uri=None, status_code=None, state=None)

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)

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)

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)
AUTHORIZATION_ENDPOINT = True

authorization_code grant type has authorization endpoint

TOKEN_ENDPOINT = True

authorization_code grant type has token endpoint

TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post', '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.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(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: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)
create_authorization_code(client, grant_user, request)

Save authorization_code for later use. Developers should implement it in subclass. Here is an example:

from authlib.common.security import generate_token

def create_authorization_code(self, client, request):
    code = generate_token(48)
    item = AuthorizationCode(
        code=code,
        client_id=client.client_id,
        redirect_uri=request.redirect_uri,
        scope=request.scope,
        user_id=grant_user.get_user_id(),
    )
    item.save()
    return code
Parameters:
  • client – the client that requesting the token.
  • grant_user – the resource owner that grant the permission.
  • request – OAuth2Request instance.
Returns:

code string

parse_authorization_code(code, client)

Get authorization_code from previously savings. Developers should implement it in subclass:

def parse_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 should 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 should 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(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: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)   |               |
+---------+                                  +---------------+
TOKEN_ENDPOINT = True

authorization_code grant type has token endpoint

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

TOKEN_ENDPOINT = True

authorization_code grant type has token endpoint

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.

TOKEN_ENDPOINT = True

authorization_code grant type has token endpoint

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 should implement this method in subclass:

def authenticate_refresh_token(self, refresh_token):
    item = Token.get(refresh_token=refresh_token)
    if item and not item.is_refresh_token_expired():
        return item
Parameters:refresh_token – The refresh token issued to the client
Returns:token
authenticate_user(credential)

Authenticate the user related to this credential. Developers should implement this method in subclass:

def authenticate_user(self, credential):
    return User.query.get(credential.user_id)
Parameters:credential – Token object
Returns:user

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)
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
request_invalid(request)

Check if the HTTP request is valid or not. Developers MUST re-implement this method. For instance, your server requires a “X-Device-Version” in the header:

def request_invalid(self, request):
    return 'X-Device-Version' in request.headers

Usually, you don’t have to detect if the request is valid or not, you can just return a False.

Parameters:request – instance of TokenRequest
Returns:Boolean
token_revoked(token)

Check if this token is revoked. Developers MUST re-implement this method. If there is a column called revoked on the token table:

def token_revoked(self, token):
    return token.revoked
Parameters:token – token instance
Returns:Boolean
class authlib.oauth2.rfc6750.BearerToken(access_token_generator, refresh_token_generator=None, expires_generator=None)

Bearer Token generator which can create the payload for token response by OAuth 2 server. A typical token response would be:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "access_token":"mF_9.B5f-4.1JqM",
    "token_type":"Bearer",
    "expires_in":3600,
    "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}
Parameters:
  • access_token_generator – a function to generate access_token.
  • refresh_token_generator – a function to generate refresh_token, if not provided, refresh_token will not be added into token response.
  • expires_generator

    The expires_generator can be an int value or a function. If it is int, all token expires_in will be this value. If it is function, it can generate expires_in depending on client and grant_type:

    def expires_generator(client, grant_type):
        if is_official_client(client):
            return 3600 * 1000
        if grant_type == 'implicit':
            return 3600
        return 3600 * 10
    
Returns:

Callable

When BearerToken is initialized, it will be callable:

token_generator = BearerToken(access_token_generator)
token = token_generator(client, grant_type, expires_in=None,
            scope=None, include_refresh_token=True)

The callable function that BearerToken created accepts these parameters:

Parameters:
  • client – the client that making the request.
  • grant_type – current requested grant_type.
  • 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

DEFAULT_EXPIRES_IN = 3600

default expires_in value

GRANT_TYPES_EXPIRES_IN = {'authorization_code': 864000, 'client_credentials': 864000, 'implicit': 3600, 'password': 864000}

default expires_in value differentiate by grant_type

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. 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
server.register_endpoint(MyRevocationEndpoint)

There is also a shortcut method to create revocation endpoint:

from authlib.flask.oauth2.sqla import create_revocation_endpoint

RevocationEndpoint = create_revocation_endpoint(db.session, Token)

# register it to authorization server
server.register_endpoint(RevocationEndpoint)

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(request, server)

Implementation of revocation endpoint which is described in RFC7009.

ENDPOINT_NAME = 'revocation'

Endpoint name to be registered

validate_endpoint_request()

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()

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, token_type_hint, client)

Get the token from database/storage by the given token string. Developers should implement this method:

def query_token(self, token, token_type_hint, client):
    if token_type_hint == 'access_token':
        return Token.query_by_access_token(token, client.client_id)
    if token_type_hint == 'refresh_token':
        return Token.query_by_refresh_token(token, client.client_id)
    return Token.query_by_access_token(token, client.client_id) or                     Token.query_by_refresh_token(token, client.client_id)
revoke_token(token)

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):
    token.revoked = True
    session.add(token)
    session.commit()
authenticate_endpoint_client()

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.rfc7515.JWS(algorithms, private_headers=None)
REGISTERED_HEADER_PARAMETER_NAMES = frozenset({'typ', 'cty', 'x5u', 'jku', 'x5t#S256', 'alg', 'crit', 'kid', 'jwk', 'x5t', 'x5c'})

Registered Header Parameter Names defined by Section 4.1

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.rfc7515.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.rfc7515.JWSObject(header, payload, type='compact')

A dict instance to represent a JWS object.

class authlib.jose.rfc7515.JWSAlgorithm

Interface for JWS algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWS with this base implementation.

prepare_private_key(key)

Prepare key for sign signature.

prepare_public_key(key)

Prepare key for verify 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, key, sig)

Verify the signature of text msg with a public/verify key.

Parameters:
  • msg – message bytes to be signed
  • key – public key to verify the signature
  • sig – result signature to be compared
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.rfc7516.JWE(algorithms, private_headers=None)
REGISTERED_HEADER_PARAMETER_NAMES = frozenset({'typ', 'cty', 'zip', 'x5u', 'jku', 'x5t#S256', 'alg', 'crit', 'kid', 'jwk', 'enc', 'x5t', 'x5c'})

Registered Header Parameter Names defined by Section 4.1

register_algorithm(algorithm)

Register an algorithm for alg or enc or zip of JWE.

serialize_compact(protected, payload, key)

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 – A string/dict 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.

Parameters:
  • s – text of JWS Compact Serialization
  • key – key used to verify the signature
  • decode – a function to decode plaintext data
Returns:

dict

class authlib.jose.rfc7516.JWEAlgorithm(name)

Interface for JWE algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWE with this base implementation.

class authlib.jose.rfc7516.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, iv, 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.rfc7516.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.rfc7517.JWK(algorithms)
loads(obj, kid=None)

Loads JSON Web Key object into a public/private key.

Parameters:
  • obj – A JWK (or JWK set) format dict
  • kid – kid of a JWK set
Returns:

key

dumps(key, kty=None, **params)

Generate JWK format for the given public/private key.

Parameters:
  • key – A public/private key
  • kty – key type of the key
  • params – Other parameters
Returns:

JWK dict

class authlib.jose.rfc7517.JWKAlgorithm
name = None

Interface for JWK algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWK with this base implementation.

prepare_key(key)

Prepare key before dumping it into JWK.

loads(obj)

Load JWK dict object into a public/private key.

dumps(key)

Dump a public/private key into JWK dict object.

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.rfc7515.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. ES384: ECDSA using P-521 and SHA-521
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.rfc7516.JWEAlgorithm. For enc, the interface are inherited from authlib.jose.rfc7516.JWEEncAlgorithm.

Current available algorithms for alg:

  1. RSA1_5: RSAES-PKCS1-v1_5
  2. RSA-OAEP: RSAES OAEP using default parameters
  3. RSA-OAEP-256: RSAES OAEP using SHA-256 and MGF1 with SHA-256
  4. A128KW: AES Key Wrap with default initial value using 128-bit key
  5. A192KW: AES Key Wrap with default initial value using 192-bit key
  6. A256KW: AES Key Wrap with default initial value using 256-bit key
  7. A128GCMKW: Key wrapping with AES GCM using 128-bit key
  8. A192GCMKW: Key wrapping with AES GCM using 192-bit key
  9. A256GCMKW: Key wrapping with AES GCM using 256-bit key

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. The interface for JWK Algorithms are all inherited from authlib.jose.rfc7517.JWKAlgorithm. The available values of kty:

  1. EC: Elliptic Curve (requires extra crypto backends)
  2. RSA: RSA (requires extra crypto backends)
  3. oct

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.rfc7519.JWT(algorithms=None, 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:

JWT

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.rfc7519.JWTClaims(payload, header, options=None, params=None)

Payload claims for JWT, which contains a validate interface.

Parameters:
  • payload – the payload dict of JWT
  • header – the header dict of JWT
  • options – validate options
  • params – other params

An example on options parameter, the format is inspired by OpenID Connect Claims:

{
    "iss": {
        "essential": True,
        "values": ["https://example.com", "https://example.org"]
    },
    "sub": {
        "essential": True
        "value": "248289761001"
    },
    "jti": {
        "validate": validate_jti
    }
}
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

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 jwk
from authlib.oauth2.rfc7523 import JWTBearerGrant as _JWTBearerGrant

class JWTBearerGrant(_JWTBearerGrant):
    def authenticate_user(self, claims):
        # get user from claims info, usually it is claims['sub']
        # for anonymous user, return None
        return None

    def authenticate_client(self, claims):
        # get client from claims, usually it is claims['iss']
        # since the assertion JWT is generated by this client
        return get_client_by_iss(claims['iss'])

    def resolve_public_key(self, headers, payload):
        # get public key to decode the assertion JWT
        jwk_set = get_client_public_keys(claims['iss'])
        return jwk.loads(jwk_set, header.get('kid'))

# 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 AssertionSession.

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 servers’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 has provided you a helper function to register client assertion authentication method easily to OAuth2Session. Take client_secret_jwt as an example:

from authlib.oauth2.rfc7523 import register_session_client_auth_method
from authlib.client import OAuth2Session

session = OAuth2Session(
    'your-client-id', 'your-client-secret',
    token_endpoint_auth_method='client_secret_jwt'
)
# just one hook here
register_session_client_auth_method(session)
session.fetch_access_token('https://example.com/oauth/token')

How about private_key_jwt? It is the same as client_secret_jwt:

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
)
register_session_client_auth_method(session)
session.fetch_access_token('https://example.com/oauth/token')
API Reference
class authlib.oauth2.rfc7523.JWTBearerGrant(request, server)
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)

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.

authenticate_user(claims)

Authenticate user with the given assertion claims. Developers MUST implement it in subclass, e.g.:

def authenticate_user(self, claims):
    return User.get_by_sub(claims['sub'])
Parameters:claims – assertion payload claims
Returns:User instance
authenticate_client(claims)

Authenticate client with the given assertion claims. Developers MUST implement it in subclass, e.g.:

def authenticate_client(self, claims):
    return Client.get_by_iss(claims['iss'])
Parameters:claims – assertion payload claims
Returns:Client instance
resolve_public_key(headers, payload)

Find public key to verify assertion signature. Developers MUST implement it in subclass, e.g.:

def resolve_public_key(self, headers, payload):
    jwk_set = get_jwk_set_by_iss(payload['iss'])
    return filter_jwk_set(jwk_set, headers['kid'])
Parameters:
  • headers – JWT headers dict
  • payload – JWT payload dict
Returns:

A public key

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
authlib.oauth2.rfc7523.register_session_client_auth_method(session, token_url=None, **kwargs)

Register “client_secret_jwt” or “private_key_jwt” token endpoint auth method to OAuth2Session.

Parameters:
  • session – OAuth2Session instance.
  • token_url – Optional token endpoint url.

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

New in version v0.10.

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. If you are using Flask, check the section Authorization Code Grant.

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 create_authorization_code method:

class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
    def create_authorization_code(self, client, grant_user, request):
        code = generate_token(48)

        # NOTICE BELOW
        code_challenge = request.data.get('code_challenge')
        code_challenge_method = request.data.get('code_challenge_method')
        item = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=grant_user.get_user_id(),
            code_challenge=code_challenge,
            code_challenge_method=code_challenge_method,
        )
        db.session.add(item)
        db.session.commit()
        return 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 framework integrations:

  1. Flask OAuth Client.
  2. Django OAuth Client.

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-a-urlsafe-string'
>>> code_challenge = create_s256_code_challenge(code_verifier)
>>> uri, state = session.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_access_token(access_token_url, authorization_response=authorization_response, code_verifier=code_verifier)

The authorization flow is the same as in OAuth 2 Session, what you need to do is:

  1. adding code_challenge and code_challenge_method in authorization_url().
  2. adding code_verifier in fetch_access_token().
API Reference
class authlib.oauth2.rfc7636.CodeChallenge(required=False)

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 create_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 CAN 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 CAN 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

RFC7662: OAuth 2.0 Token Introspection

This section contains the generic implementation of RFC7662.

Register Introspection Endpoint

With the help of register_endpoint offered by Flask OAuth 2.0 Server, we can easily add introspection endpoint to the authorization 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, 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 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,
        }

# 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)
API Reference
class authlib.oauth2.rfc7662.IntrospectionEndpoint(request, server)

Implementation of introspection endpoint which is described in RFC7662.

ENDPOINT_NAME = 'introspection'

Endpoint name to be registered

validate_endpoint_request()

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()

Validate introspection request and create the response.

Returns:(status_code, body, headers)
query_token(token, token_type_hint, client)

Get the token from database/storage by the given token string. Developers should implement this method:

def query_token(self, token, token_type_hint, client):
    if token_type_hint == 'access_token':
        return Token.query_by_access_token(token, client.client_id)
    if token_type_hint == 'refresh_token':
        return Token.query_by_refresh_token(token, client.client_id)
    return Token.query_by_access_token(token, client.client_id) or                     Token.query_by_refresh_token(token, client.client_id)
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()

Authentication client for endpoint with CLIENT_AUTH_METHODS.

OpenID Connect 1.0

This part of the documentation covers the specification of OpenID Connect. Learn how to use it in Flask OpenID Connect 1.0.

OpenID Grants
class authlib.oidc.core.grants.OpenIDCodeGrant(*args, **kwargs)

Bases: authlib.oauth2.rfc6749.grants.authorization_code.AuthorizationCodeGrant

class authlib.oidc.core.grants.OpenIDImplicitGrant(request, server)

Bases: authlib.oauth2.rfc6749.grants.implicit.ImplicitGrant

create_authorization_response(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:grant_user – if resource owner granted the request, pass this resource owner, otherwise pass None.
Returns:(status_code, body, headers)
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(*args, **kwargs)

Bases: authlib.oauth2.rfc6749.grants.authorization_code.AuthorizationCodeGrant

create_authorization_response(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:grant_user – if resource owner granted the request, pass this resource owner, otherwise pass None.
Returns:(status_code, body, headers)
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).

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.

Community

This section aims to make Authlib sustainable, on governance, code commits, issues and finance.

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.

    Become a backer or sponsor via Patreon

  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.

Projects

Open source projects that are using Authlib to create an OAuth server.

Example OAuth 2.0 Server

An official example on how to create an OAuth 2.0 server with Authlib.

uData

Customizable and skinnable social platform dedicated to (open)data.

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, if this license doesn’t fit your company, consider to purchase a commercial license.

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 a list of the main contributors.

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 0.11: Kaori

Released on Apr 6, 2019.

BIG NEWS: Authlib has changed its open source license from AGPL to BSD.

Important Changes: Authlib specs module has been split into jose, oauth1, oauth2, and oidc. Find how to solve the deprecate issues via https://git.io/fjvpt

RFC implementations and updates in this release:

  • RFC7518: Added A128GCMKW, A192GCMKW, A256GCMKW algorithms for JWE.
  • RFC5849: Removed draft-eaton-oauth-bodyhash-00 spec for OAuth 1.0.

Small changes and bug fixes in this release:

  • Fixed missing scope on password and client_credentials grant types of OAuth2Session via issue#96.
  • Fixed Flask OAuth client cache detection via issue#98.
  • Enabled ssl certificates for OAuth2Session via PR#100, thanks to pingz.
  • Fixed error response for invalid/expired refresh token via issue#112.
  • Fixed error handle for invalid redirect uri via issue#113.
  • Fixed error response redirect to fragment via issue#114.
  • Fixed non-compliant responses from RFC7009 via issue#119.

Experiment Features: There is an experiment aiohttp client for OAuth1 and OAuth2 in authlib.client.aiohttp.

Version 0.10: Kluke

Released on Oct 12, 2018.

The most important change in this version is grant extension system. When registering a grant, developers can pass extensions to the grant:

authorization_server.register_grant(GrantClass, [extension])

Find Flask Grant Extensions implementation.

RFC implementations and updates in this release:

Besides that, there are other improvements:

  • Export save_authorize_state method on Flask and Django client
  • Add fetch_token to Django OAuth client
  • Add scope operator for @require_oauth Multiple Scopes
  • Fix two OAuth clients in the same Flask route PR#85

Deprecate Changes: find how to solve the deprecate issues via https://git.io/fAmW1

Version 0.9: Ponyo

Released on Aug 12, 2018. Fun Dive.

There is no big break changes in this version. The very great improvement is adding JWE support. But the JWA parts of JWE are not finished yet, use with caution.

RFC implementations in this release:

Other Changes:

  • Fixed the lazy initialization of Flask OAuth 2.0 provider.
  • Deprecated authlib.client.apps from v0.7 has been dropped.

Version 0.8: Arutoria

Released on Jun 17, 2018. Try Django.

Authlib has tried to introduce Django OAuth server implementation in this version. It turns out that it is not that easy. In this version, only Django OAuth 1.0 server is provided.

As always, there are also RFC features added in this release, here is what’s in version 0.8:

Improvement in this release:

  • A new redesigned error system. All errors are subclasses of a AuthlibBaseError.
  • I18N support for error descriptions.
  • Separate AuthorizationCodeMixin in authlib.flask.oauth2.sqla via issue#57.
  • Add context information when generate token via issue#58.
  • Improve JWT key handles, auto load JWK and JWK set.
  • Add require_oauth.acquire with statement, get example on Flask OAuth 2.0 Server.

Deprecate Changes: find how to solve the deprecate issues via https://git.io/vhL75

  • Rename config key OAUTH2_EXPIRES_IN to OAUTH2_TOKEN_EXPIRES_IN.
  • Rename Flask OAuth 2.0 create_expires_generator to create_token_expires_in_generator

Version 0.7: Honami

Released on Apr 28, 2018. Better Beta.

Authlib has changed its license from LGPL to AGPL. This is not a huge release like v0.6, but it still contains some deprecate changes, the good news is they are compatible, they won’t break your project. Authlib can’t go further without these deprecate changes.

As always, Authlib is adding specification implementations. Here is what’s in version 0.7:

Besides that, there are more changes:

  • Add overwrite parameter for framework integrations clients.
  • Add response_mode=query for OpenID Connect implicit and hybrid flow.
  • Bug fix and documentation fix via issue#42, issue#43.
  • Dropping authlib.client.apps. Use Loginpass instead.

Deprecate Changes: find how to solve the deprecate issues via https://git.io/vpCH5

Version 0.6: Matoi

Released on Mar 20, 2018. Going Beta!

From alpha to beta. This is a huge release with lots of deprecating changes and some breaking changes. And finally, OpenID Connect server is supported by now, because Authlib has added these specifications:

The specifications are not completed yet, but they are ready to use. The missing RFC7516 (JWE) is going to be implemented in next version. Open ID Connect 1.0 is added with:

Besides that, there are more changes:

  • Implementation of RFC7662: OAuth 2.0 Token Introspection via PR#36.
  • Use the token_endpoint_auth_method concept defined in RFC7591.
  • Signal feature for Flask integration of OAuth 2.0 server.
  • Bug fixes for OAuth client parts, thanks for the instruction by Lukas Schink.

Breaking Changes:

  1. the columns in authlib.flask.oauth2.sqla has been changed a lot. If you are using it, you need to upgrade your database.
  2. use register_token_validator on ResourceProtector.
  3. authlib.client.oauth1.OAuth1 has been renamed to authlib.client.oauth1.OAuth1Auth.

Deprecate Changes: find how to solve the deprecate issues via https://git.io/vAAUK

Version 0.5.1

Released on Feb 11, 2018.

Just a quick bug fix release.

  • Fixed OAuth2Session.request with auth.

Version 0.5: Kirie

Released on Feb 11, 2018. Breaking Changes!

This version breaks a lot of things. There are many redesigns in order to get a better stable API. It is still in Alpha stage, with these breaking changes, I hope Authlib will go into Beta in the next version.

  • Added register_error_uri() and its Flask integration.
  • OAuth2Session supports more grant types.
  • Deprecate built-in cache. Read more on issue#23.
  • Redesigned OAuth 1 Flask server. Read the docs Flask OAuth 1.0 Server.
  • Deprecate client_model. Read more on issue#27.
  • Breaking change on AuthorizationCodeGrant.create_authorization_code, last parameter is changed to an OAuth2Request instance.
  • Rename callback_uri to redirect_uri in client.

Version 0.4.1

Released on Feb 2, 2018. A Quick Bugfix

  • Fixed missing code params when fetching access token. This bug is introduced when fixing issue#16.

Version 0.4: Tsukino

Released on Jan 31, 2018. Enjoy the Super Blue Blood Moon!

This is a feature releasing for OAuth 1 server. Things are not settled yet, there will still be breaking changes in the future. Some of the breaking changes are compatible with deprecated messages, a few are not. I’ll keep the deprecated message for 2 versions. Here is the main features:

  • RFC5847, OAuth 1 client and server
  • Flask implementation of OAuth 1 authorization server and resource protector.
  • Mixin of SQLAlchemy models for easy integration with OAuth 1.

In version 0.4, there is also several bug fixes. Thanks for the early contributors.

  • Allow Flask OAuth register fetch_token and update_token.
  • Bug fix for OAuthClient when refresh_token_params is None via PR#14.
  • Don’t pass everything in request args for Flask OAuth client via issue#16.
  • Bug fix for IDToken.validate_exp via issue#17.

Deprecated Changes

There are parameters naming changes in the client part:

  • client_key has been changed to client_id
  • resource_owner_key has been changed to token
  • resource_owner_secret has been changed to token_secret

Currently, they are backward compatible. You will be notified by warnings.

Old Versions

  • Version 0.3: Released on Dec 24, 2017
  • Version 0.2.1: Released on Dec 6, 2017
  • Version 0.2: Released on Nov 25, 2017
  • Version 0.1: Released on Nov 18, 2017

Consider to follow Authlib on Twitter, and subscribe Authlib Blog.