Release v1.1.0. (Installation)
The ultimate Python library in building OAuth and OpenID Connect servers. It is designed from low level specifications implementations to high level frameworks integrations, to meet the needs of everyone.
Authlib is compatible with Python3.6+.
This part of the documentation begins with some background information about Authlib, and installation of Authlib. Then it will explain OAuth 1.0, OAuth 2.0, and JOSE. At last, it shows the implementation in frameworks, and libraries such as Flask, Django, Requests, HTTPX, Starlette, FastAPI, and etc.
This part of the documentation begins with some background information about Authlib, and installation of Authlib.
Authlib is the ultimate Python library in building OAuth and OpenID Connect clients and servers. It offers generic implementations of RFCs, including OAuth 1.0, OAuth 2.0, JWT and many more. It becomes a Monolithic project that powers from low-level specification implementation to high-level framework integrations.
I’m intended to make it profitable so that it can be Sustainable, check out the Funding section.
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.
Authlib is designed as flexible as possible. Since it is built from low-level specification implementation to high-level framework integrations, if a high level can’t meet your needs, you can always create one for your purpose based on the low-level implementation.
Most of the cases, you don’t need to do so. Extendable has been taken
into account from the start of the project. Take OAuth 2.0 server as an
example, instead of a pre-configured server, Authlib takes advantage of
register
.
authorization_server.register_grant(AuthorizationCodeGrant)
authorization_server.register_endpoint(RevocationEndpoint)
If you find anything not that extendable, you can ask help on StackOverflow or open an issue on GitHub.
This project is inspired by:
OAuthLib
Flask-OAuthlib
requests-oauthlib
pyjwt
This part of the documentation covers the installation of Authlib, just like any other software package needs to be installed first.
Installing Authlib is simple with pip:
$ pip install Authlib
It will also install the dependencies:
cryptography
Note
You may enter problems when installing cryptography, check its official document at https://cryptography.io/en/latest/installation/
Using Authlib with requests:
$ pip install Authlib requests
Using Authlib with httpx:
$ pip install Authlib httpx
Using Authlib with Flask:
$ pip install Authlib Flask
Using Authlib with Django:
$ pip install Authlib Django
Using Authlib with Starlette:
$ pip install Authlib httpx Starlette
Changed in version v0.12: “requests” is an optional dependency since v0.12. If you want to use Authlib client, you have to install “requests” by yourself:
$ pip install Authlib requests
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 .
You can always enable debug logging when you run into issues in your code:
import logging
import sys
log = logging.getLogger('authlib')
log.addHandler(logging.StreamHandler(sys.stdout))
log.setLevel(logging.DEBUG)
We are still designing the logging system. (TBD)
This part of the documentation contains information on the client parts. Authlib provides many frameworks integrations, including:
The famous Python Requests
A next generation HTTP client for Python: httpx
Flask web framework integration
Django web framework integration
Starlette web framework integration
FastAPI web framework integration
In order to use Authlib client, you have to install each library yourself. For
example, you want to use requests
OAuth clients:
$ pip install Authlib requests
For instance, you want to use httpx
OAuth clients:
$ pip install -U Authlib httpx
Here is a simple overview of Flask OAuth client:
from flask import Flask, jsonify
from authlib.integrations.flask_client import OAuth
app = Flask(__name__)
oauth = OAuth(app)
github = oauth.register('github', {...})
@app.route('/login')
def login():
redirect_uri = url_for('authorize', _external=True)
return github.authorize_redirect(redirect_uri)
@app.route('/authorize')
def authorize():
token = github.authorize_access_token()
# you can save the token into database
profile = github.get('/user', token=token)
return jsonify(profile)
Follow the documentation below to find out more in detail.
This documentation covers the common design of a Python OAuth 1.0 client. Authlib provides three implementations of OAuth 1.0 client:
requests_client.OAuth1Session
implementation of OAuth for Requests,
which is a replacement for requests-oauthlib.
httpx_client.AsyncOAuth1Client
implementation of OAuth for HTTPX,
which is an async OAuth 1.0 client.
requests_client.OAuth1Session
and httpx_client.AsyncOAuth1Client
shares the same API.
There are also frameworks integrations of Flask OAuth Client, Django OAuth Client and Starlette OAuth Client. If you are using these frameworks, you may have interests in their own documentation.
If you are not familiar with OAuth 1.0, it is better to read Introduce OAuth 1.0 now.
There are three steps in OAuth 1 to obtain an access token:
fetch a temporary credential
visit the authorization page
exchange access token with the temporary credential
But first, we need to initialize an OAuth 1.0 client:
>>> client_id = 'Your Twitter client key'
>>> client_secret = 'Your Twitter client secret'
>>> # using requests client
>>> from authlib.integrations.requests_client import OAuth1Session
>>> client = OAuth1Session(client_id, client_secret)
>>> # using httpx client
>>> from authlib.integrations.httpx_client import AsyncOAuth1Client
>>> client = AsyncOAuth1Client(client_id, client_secret)
The first step is to fetch temporary credential, which will be used to generate authorization URL:
>>> request_token_url = 'https://api.twitter.com/oauth/request_token'
>>> request_token = client.fetch_request_token(request_token_url)
>>> print(request_token)
{'oauth_token': 'gA..H', 'oauth_token_secret': 'lp..X', 'oauth_callback_confirmed': 'true'}
Save this temporary credential for later use (if required).
You can assign a redirect_uri
before fetching the request token, if
you want to redirect back to another URL other than the one you registered:
>>> client.redirect_uri = 'https://your-domain.org/auth'
>>> client.fetch_request_token(request_token_url)
When the authorization is granted, you will be redirected back to your registered callback URI. For instance:
https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq
If you assigned redirect_uri
in Fetch Access Token, the
authorize response would be something like:
https://your-domain.org/auth?oauth_token=gA..H&oauth_verifier=fcg..1Dq
Now fetch the access token with this response:
>>> resp_url = 'https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq'
>>> client.parse_authorization_response(resp_url)
>>> access_token_url = 'https://api.twitter.com/oauth/access_token'
>>> token = client.fetch_access_token(access_token_url)
>>> print(token)
{
'oauth_token': '12345-st..E',
'oauth_token_secret': 'o67..X',
'user_id': '12345',
'screen_name': 'lepture',
'x_auth_expires': '0'
}
>>> save_access_token(token)
Save this token to access protected resources.
The above flow is not always what we will use in a real project. When we are redirected to authorization endpoint, our session is over. In this case, when the authorization server send us back to our server, we need to create another session:
>>> # restore your saved request token, which is a dict
>>> request_token = restore_request_token()
>>> oauth_token = request_token['oauth_token']
>>> oauth_token_secret = request_token['oauth_token_secret']
>>> from authlib.integrations.requests_client import OAuth1Session
>>> # if using httpx: from authlib.integrations.httpx_client import AsyncOAuth1Client
>>> client = OAuth1Session(
... client_id, client_secret,
... token=oauth_token,
... token_secret=oauth_token_secret)
>>> # there is no need for `parse_authorization_response` if you can get `verifier`
>>> verifier = request.args.get('verifier')
>>> access_token_url = 'https://api.twitter.com/oauth/access_token'
>>> token = client.fetch_access_token(access_token_url, verifier)
Now you can access the protected resources. If you re-use the session, you don’t need to do anything:
>>> account_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
>>> resp = client.get(account_url)
<Response [200]>
>>> resp.json()
{...}
The above is not the real flow, just like what we did in Fetch Access Token, we need to create another session ourselves:
>>> access_token = restore_access_token_from_database()
>>> oauth_token = access_token['oauth_token']
>>> oauth_token_secret = access_token['oauth_token_secret']
>>> # if using httpx: from authlib.integrations.httpx_client import AsyncOAuth1Client
>>> client = OAuth1Session(
... client_id, client_secret,
... token=oauth_token,
... token_secret=oauth_token_secret)
>>> account_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
>>> resp = client.get(account_url)
Please note, there are duplicated steps in the documentation, read carefully and ignore the duplicated explains.
It is also possible to access protected resources with OAuth1Auth
object.
Create an instance of OAuth1Auth with an access token:
# if using requests
from authlib.integrations.requests_client import OAuth1Auth
# if using httpx
from authlib.integrations.httpx_client import OAuth1Auth
auth = OAuth1Auth(
client_id='..',
client_secret=client_secret='..',
token='oauth_token value',
token_secret='oauth_token_secret value',
...
)
If using requests
, pass this auth
to access protected resources:
import requests
url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
resp = requests.get(url, auth=auth)
If using httpx
, pass this auth
to access protected resources:
import httpx
url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
resp = await httpx.get(url, auth=auth)
Changed in version v0.13: All client related code have been moved into authlib.integrations
. For
earlier versions of Authlib, check out their own versions documentation.
This documentation covers the common design of a Python OAuth 2.0 client. Authlib provides three implementations of OAuth 2.0 client:
requests_client.OAuth2Session
implementation of OAuth for Requests,
which is a replacement for requests-oauthlib.
httpx_client.AsyncOAuth2Client
implementation of OAuth for HTTPX,
which is async OAuth 2.0 client powered by HTTPX.
requests_client.OAuth2Session
and httpx_client.AsyncOAuth2Client
shares the same API.
There are also frameworks integrations of Flask OAuth Client, Django OAuth Client and Starlette OAuth Client. If you are using these frameworks, you may have interests in their own documentation.
If you are not familiar with OAuth 2.0, it is better to read Introduce OAuth 2.0 now.
OAuth2Session supports implicit grant type. It can fetch the access token with
the response_type
of token
:
>>> uri, state = client.create_authorization_url(authorization_endpoint, response_type='token')
>>> print(uri)
https://some-service.com/oauth/authorize?response_type=token&client_id=be..4d&...
Visit this link, and grant the authorization, the OAuth authorization server will redirect back to your redirect_uri, the response url would be something like:
https://example.com/cb#access_token=2..WpA&state=xyz&token_type=bearer&expires_in=3600
Fetch access token from the fragment with .fetch_token
method:
>>> token = client.fetch_token(authorization_response=authorization_response)
>>> # if you don't specify access token endpoint, it will fetch from fragment.
>>> print(token)
{'access_token': '2..WpA', 'token_type': 'bearer', 'expires_in': 3600}
Note
GitHub doesn’t support token
response type, try with other services.
The password
grant type is supported since Version 0.5. Use username
and password
to fetch the access token:
>>> token = client.fetch_token(token_endpoint, username='a-name', password='a-password')
The client_credentials
grant type is supported since Version 0.5. If no
code
or no user info provided, it would be a client_credentials
request. But it is suggested that you specify a grant_type
for it:
>>> token = client.fetch_token(token_endpoint)
>>> # or with grant_type
>>> token = client.fetch_token(token_endpoint, grant_type='client_credentials')
When fetching access token, the authorization server will require a client authentication, Authlib provides three default methods defined by RFC7591:
client_secret_basic
client_secret_post
none
The default value is client_secret_basic
. You can change the auth method
with token_endpoint_auth_method
:
>>> client = OAuth2Session(token_endpoint_auth_method='client_secret_post')
If the authorization server requires other means of authentication, you can
construct an auth
for your own need, and pass it to fetch_token
:
>>> auth = YourAuth(...)
>>> token = client.fetch_token(token_endpoint, auth=auth, ...)
It is also possible to extend the client authentication method with
.register_client_auth_method
. Besides the default three authentication
methods, there are more provided by Authlib. e.g.
client_secret_jwt
private_key_jwt
These two methods are defined by RFC7523 and OpenID Connect. Find more in Using JWTs Client Assertion in OAuth2Session.
There are still cases that developers need to define a custom client
authentication method. Take issue#158 as an example, the provider
requires us put client_id
and client_secret
on URL when sending
POST request:
POST /oauth/token?grant_type=code&code=...&client_id=...&client_secret=...
Let’s call this weird authentication method client_secret_uri
, and this
is how we can get our OAuth 2.0 client authenticated:
from authlib.common.urls import add_params_to_uri
def auth_client_secret_uri(client, method, uri, headers, body):
uri = add_params_to_uri(uri, [
('client_id', client.client_id),
('client_secret', client.client_secret),
])
uri = uri + '&' + body
body = ''
return uri, headers, body
client = OAuth2Session(
'client_id', 'client_secret',
token_endpoint_auth_method='client_secret_uri',
...
)
client.register_client_auth_method(('client_secret_uri', auth_client_secret_uri))
With client_secret_uri
registered, OAuth 2.0 client will authenticate with
the signed URI. It is also possible to assign the function to token_endpoint_auth_method
directly:
client = OAuth2Session(
'client_id', 'client_secret',
token_endpoint_auth_method=auth_client_secret_uri,
)
Now you can access the protected resources. If you re-use the session, you don’t need to do anything:
>>> account_url = 'https://api.github.com/user'
>>> resp = client.get(account_url)
<Response [200]>
>>> resp.json()
{...}
The above is not the real flow, just like what we did in Fetch Token, we need to create another session ourselves:
>>> token = restore_previous_token_from_database()
>>> # token is a dict which must contain ``access_token``, ``token_type``
>>> client = OAuth2Session(client_id, client_secret, token=token)
>>> account_url = 'https://api.github.com/user'
>>> resp = client.get(account_url)
It is possible that your previously saved token is expired when accessing protected resources. In this case, we can refresh the token manually, or even better, Authlib will refresh the token automatically and update the token for us.
If your OAuth2Session
class was created with the
token_endpoint parameter, Authlib will automatically refresh the token when
it has expired:
>>> openid_configuration = requests.get("https://example.org/.well-known/openid-configuration").json()
>>> session = OAuth2Session(…, token_endpoint=openid_configuration["token_endpoint"])
To call refresh_token()
manually means
we are going to exchange a new “access_token” with “refresh_token”:
>>> token = restore_previous_token_from_database()
>>> new_token = client.refresh_token(token_endpoint, refresh_token=token.refresh_token)
Authlib can also refresh a new token automatically when requesting resources.
This is done by passing a update_token
function when constructing the client
instance:
def update_token(token, refresh_token=None, access_token=None):
if refresh_token:
item = OAuth2Token.find(name=name, refresh_token=refresh_token)
elif access_token:
item = OAuth2Token.find(name=name, access_token=access_token)
else:
return
# update old token
item.access_token = token['access_token']
item.refresh_token = token.get('refresh_token')
item.expires_at = token['expires_at']
item.save()
client = OAuth2Session(client_id, client_secret, update_token=update_token)
When sending a request to resources endpoint, if our previously saved token
is expired, this client
will invoke .refresh_token
method itself and
call this our defined update_token
to save the new token:
token = restore_previous_token_from_database()
client.token = token
# if the token is expired, this GET request will update token
client.get('https://openidconnect.googleapis.com/v1/userinfo')
If the provider support token revocation and introspection, you can revoke and introspect the token with:
token_endpoint = 'https://example.com/oauth/token'
token = get_your_previous_saved_token()
client.revoke_token(token_endpoint, token=token)
client.introspect_token(token_endpoint, token=token)
You can find the available parameters in API docs:
There are services that claimed they are providing OAuth API, but with a little differences. Some services even return with the wrong Content Type. Compliance hooks are provided to solve those problems:
access_token_response
: invoked before token parsing.
refresh_token_response
: invoked before refresh token parsing.
protected_request
: invoked before making a request.
For instance, Stackoverflow MUST add a site parameter in query string to protect users’ resources. And stackoverflow’s response is not in JSON. Let’s fix it:
from authlib.common.urls import add_params_to_uri, url_decode
def _non_compliant_param_name(url, headers, data):
params = {'site': 'stackoverflow'}
url = add_params_to_uri(url, params)
return url, headers, body
def _fix_token_response(resp):
data = dict(url_decode(resp.text))
data['token_type'] = 'Bearer'
data['expires_in'] = int(data['expires'])
resp.json = lambda: data
return resp
session.register_compliance_hook(
'protected_request', _non_compliant_param_name)
session.register_compliance_hook(
'access_token_response', _fix_token_response)
If you find a non standard OAuth 2 services, and you can’t fix it. Please report it in GitHub issues.
For services that support OpenID Connect, if a scope of openid
is provided,
the authorization server will return a value of id_token
in response:
>>> client_id = 'Your Google client ID'
>>> client_secret = 'Your Google client secret'
>>> scope = 'openid email profile'
>>> # using requests
>>> client = OAuth2Session(client_id, client_secret, scope=scope)
>>> # using httpx
>>> client = AsyncOAuth2Client(client_id, client_secret, scope=scope)
The remote server may require other parameters for OpenID Connect requests, for
instance, it may require a nonce
parameter, in this case, you need to
generate it yourself, and pass it to create_authorization_url
:
>>> from authlib.common.security import generate_token
>>> # remember to save this nonce for verification
>>> nonce = generate_token()
>>> client.create_authorization_url(url, redirect_uri='xxx', nonce=nonce, ...)
At the last step of client.fetch_token
, the return value contains
a id_token
:
>>> resp = session.fetch_token(...)
>>> print(resp['id_token'])
This id_token
is a JWT text, it can not be used unless it is parsed.
Authlib has provided tools for parsing and validating OpenID Connect id_token:
>>> from authlib.oidc.core import CodeIDToken
>>> from authlib.jose import jwt
>>> # GET keys from https://www.googleapis.com/oauth2/v3/certs
>>> claims = jwt.decode(resp['id_token'], keys, claims_cls=CodeIDToken)
>>> claims.validate()
Get deep inside with JsonWebToken
and
CodeIDToken
. Learn how to validate JWT claims
at JSON Web Token (JWT).
AssertionSession
is a Requests Session for Assertion
Framework of OAuth 2.0 Authorization Grants. It is also know as service account.
A configured AssertionSession
with handle token authorization automatically,
which means you can just use it.
Take Google Service Account as an example, with the information in your service account JSON configure file:
import json
from authlib.integrations.requests_client import AssertionSession
with open('MyProject-1234.json') as f:
conf = json.load(f)
token_uri = conf['token_uri']
header = {'alg': 'RS256'}
key_id = conf.get('private_key_id')
if key_id:
header['kid'] = key_id
# Google puts scope in payload
claims = {'scope': scope}
session = AssertionSession(
token_endpoint=token_uri,
issuer=conf['client_email'],
audience=token_uri,
claims=claims,
subject=None,
key=conf['private_key'],
header=header,
)
session.get(...)
session.post(...)
There is a ready to use GoogleServiceAccount
in loginpass. You can
also read these posts:
Requests is a very popular HTTP library for Python. Authlib enables OAuth 1.0
and OAuth 2.0 for Requests with its OAuth1Session
, OAuth2Session
and AssertionSession
.
There are three steps in OAuth 1 Session to obtain an access token:
fetch a temporary credential
visit the authorization page
exchange access token with the temporary credential
It shares a common API design with OAuth for HTTPX.
The requests integration follows our common guide of OAuth 1 Session. Follow the documentation in OAuth 1 Session instead.
It is also possible to use OAuth1Auth
directly with in requests.
After we obtained access token from an OAuth 1.0 provider, we can construct
an auth
instance for requests:
auth = OAuth1Auth(
client_id='YOUR-CLIENT-ID',
client_secret='YOUR-CLIENT-SECRET',
token='oauth_token',
token_secret='oauth_token_secret',
)
requests.get(url, auth=auth)
In OAuth 2 Session, there are many grant types, including:
Authorization Code Flow
Implicit Flow
Password Flow
Client Credentials Flow
And also, Authlib supports non Standard OAuth 2.0 providers via Compliance Fix.
Follow the common guide of OAuth 2 Session to find out how to use requests integration of OAuth 2.0 flow.
client_secret_jwt
in Requests¶There are three default client authentication methods defined for
OAuth2Session
. But what if you want to use client_secret_jwt
instead?
client_secret_jwt
is defined in RFC7523, use it for Requests:
from authlib.integrations.requests_client import OAuth2Session
from authlib.oauth2.rfc7523 import ClientSecretJWT
token_endpoint = 'https://example.com/oauth/token'
session = OAuth2Session(
'your-client-id', 'your-client-secret',
token_endpoint_auth_method=ClientSecretJWT(token_endpoint),
)
session.fetch_token(token_endpoint)
The ClientSecretJWT
is provided by RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.
private_key_jwt
in Requests¶What if you want to use private_key_jwt
client authentication method,
here is the way with PrivateKeyJWT
for Requests:
from authlib.integrations.requests_client import OAuth2Session
from authlib.oauth2.rfc7523 import PrivateKeyJWT
with open('your-private-key.pem', 'rb') as f:
private_key = f.read()
token_endpoint = 'https://example.com/oauth/token'
session = OAuth2Session(
'your-client-id', private_key,
token_endpoint_auth_method=PrivateKeyJWT(token_endpoint),
)
session.fetch_token(token_endpoint)
The PrivateKeyJWT
is provided by RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.
Already obtained access token? We can use OAuth2Auth
directly in
requests. But this OAuth2Auth can not refresh token automatically for you.
Here is how to use it in requests:
token = {'token_type': 'bearer', 'access_token': '....', ...}
auth = OAuth2Auth(token)
requests.get(url, auth=auth)
OpenID Connect is built on OAuth 2.0. It is pretty simple to communicate with
an OpenID Connect provider via Authlib. With Authlib built-in OAuth 2.0 system
and JsonWebToken (JWT), parsing OpenID Connect id_token
could be very easy.
Understand how it works with OAuth 2 OpenID Connect.
The Assertion Framework of OAuth 2.0 Authorization Grants is also known as
service account. With the implementation of AssertionSession
, we can
easily integrate with a “assertion” service.
Checking out an example of Google Service Account with AssertionSession.
Developers SHOULD close a Requests Session when the jobs are done. You
can call .close()
manually, or use a with
context to automatically
close the session:
session = OAuth2Session(client_id, client_secret)
session.get(url)
session.close()
with OAuth2Session(client_id, client_secret) as session:
session.get(url)
Self-signed certificate mutual-TLS method internet standard is defined in RFC8705 Section 2.2 .
For specifics development purposes only, you may need to disable SSL verification.
You can force all requests to disable SSL verification by setting
your environment variable CURL_CA_BUNDLE=""
.
This solutions works because Python requests (and most of the packages)
overwrites the default value for ssl verifications from environment
variables CURL_CA_BUNDLE
and REQUESTS_CA_BUNDLE
.
This hack will only work with CURL_CA_BUNDLE
, as you can see
in requests/sessions.py
verify = (os.environ.get('REQUESTS_CA_BUNDLE')
or os.environ.get('CURL_CA_BUNDLE'))
Please remember to set the env variable only in you development environment.
HTTPX is a next-generation HTTP client for Python. Authlib enables OAuth 1.0 and OAuth 2.0 for HTTPX with its async versions:
AssertionClient
Note
HTTPX is still in its “alpha” stage, use it with caution.
There are three steps in OAuth 1 to obtain an access token:
fetch a temporary credential
visit the authorization page
exchange access token with the temporary credential
It shares a common API design with OAuth for Requests.
Read the common guide of OAuth 1 Session to understand the whole OAuth 1.0 flow.
In OAuth 2 Session, there are many grant types, including:
Authorization Code Flow
Implicit Flow
Password Flow
Client Credentials Flow
And also, Authlib supports non Standard OAuth 2.0 providers via Compliance Fix.
Read the common guide of OAuth 2 Session to understand the whole OAuth 2.0 flow.
client_secret_jwt
in HTTPX¶Here is how you could register and use client_secret_jwt
client
authentication method for HTTPX:
from authlib.integrations.httpx_client import AsyncOAuth2Client
from authlib.oauth2.rfc7523 import ClientSecretJWT
client = AsyncOAuth2Client(
'your-client-id', 'your-client-secret',
token_endpoint_auth_method='client_secret_jwt'
)
token_endpoint = 'https://example.com/oauth/token'
client.register_client_auth_method(ClientSecretJWT(token_endpoint))
client.fetch_token(token_endpoint)
The ClientSecretJWT
is provided by RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.
private_key_jwt
in HTTPX¶Here is how you could register and use private_key_jwt
client
authentication method for HTTPX:
from authlib.integrations.httpx_client import AsyncOAuth2Client
from authlib.oauth2.rfc7523 import PrivateKeyJWT
with open('your-private-key.pem', 'rb') as f:
private_key = f.read()
client = AsyncOAuth2Client(
'your-client-id', private_key,
token_endpoint_auth_method='private_key_jwt',
)
token_endpoint = 'https://example.com/oauth/token'
client.register_client_auth_method(PrivateKeyJWT(token_endpoint))
client.fetch_token(token_endpoint)
The PrivateKeyJWT
is provided by RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.
The async version of AsyncOAuth1Client
works the same as
OAuth 1 Session, except that we need to add await
when
required:
# fetching request token
request_token = await client.fetch_request_token(request_token_url)
# fetching access token
access_token = await client.fetch_access_token(access_token_url)
# normal requests
await client.get(...)
await client.post(...)
await client.put(...)
await client.delete(...)
The async version of AsyncOAuth2Client
works the same as
OAuth 2 Session, except that we need to add await
when
required:
# fetching access token
token = await client.fetch_token(token_endpoint, ...)
# normal requests
await client.get(...)
await client.post(...)
await client.put(...)
await client.delete(...)
The AsyncOAuth2Client
also supports update_token
parameter,
the update_token
can either be sync and async. For instance:
async def update_token(token, refresh_token=None, access_token=None):
if refresh_token:
item = await OAuth2Token.find(name=name, refresh_token=refresh_token)
elif access_token:
item = await OAuth2Token.find(name=name, access_token=access_token)
else:
return
# update old token
item.access_token = token['access_token']
item.refresh_token = token.get('refresh_token')
item.expires_at = token['expires_at']
await item.save()
Then pass this update_token
into AsyncOAuth2Client
.
AsyncAssertionClient
is the async version for Assertion Framework of
OAuth 2.0 Authorization Grants. It is also know as service account. A configured
AsyncAssertionClient
will handle token authorization automatically,
which means you can just use it.
Take Google Service Account as an example, with the information in your service account JSON configure file:
import json
from authlib.integrations.httpx_client import AsyncAssertionClient
with open('MyProject-1234.json') as f:
conf = json.load(f)
token_uri = conf['token_uri']
header = {'alg': 'RS256'}
key_id = conf.get('private_key_id')
if key_id:
header['kid'] = key_id
# Google puts scope in payload
claims = {'scope': scope}
async def main():
client = AsyncAssertionClient(
token_endpoint=token_uri,
issuer=conf['client_email'],
audience=token_uri,
claims=claims,
subject=None,
key=conf['private_key'],
header=header,
)
resp = await client.get(...)
resp = await client.post(...)
Developers SHOULD close a HTTPX Session when the jobs are done. You
can call .close()
manually, or use a with
context to automatically
close the session:
client = OAuth2Client(client_id, client_secret)
client.get(url)
client.close()
with OAuth2Client(client_id, client_secret) as client:
client.get(url)
For async OAuth Client, use await client.close()
:
client = AsyncOAuth2Client(client_id, client_secret)
await client.get(url)
await client.close()
async with AsyncOAuth2Client(client_id, client_secret) as client:
await client.get(url)
Our Web OAuth Clients will close every session automatically, no need to worry.
This documentation covers OAuth 1.0 and OAuth 2.0 integrations for Python Web Frameworks like:
Django: The web framework for perfectionists with deadlines
Flask: The Python micro framework for building web applications
Starlette: The little ASGI framework that shines
Authlib shares a common API design among these web frameworks. Instead of introducing them one by one, this documentation contains the common usage for them all.
We start with creating a registry with the OAuth
class:
# for Flask framework
from authlib.integrations.flask_client import OAuth
# for Django framework
from authlib.integrations.django_client import OAuth
# for Starlette framework
from authlib.integrations.starlette_client import OAuth
oauth = OAuth()
There are little differences among each framework, you can read their documentation later:
The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.
For instance, Twitter is an OAuth 1.0 service, you want your users to log in your website with Twitter.
The first step is register a remote application on the OAuth
registry via
oauth.register
method:
oauth.register(
name='twitter',
client_id='{{ your-twitter-consumer-key }}',
client_secret='{{ your-twitter-consumer-secret }}',
request_token_url='https://api.twitter.com/oauth/request_token',
request_token_params=None,
access_token_url='https://api.twitter.com/oauth/access_token',
access_token_params=None,
authorize_url='https://api.twitter.com/oauth/authenticate',
authorize_params=None,
api_base_url='https://api.twitter.com/1.1/',
client_kwargs=None,
)
The first parameter in register
method is the name of the remote
application. You can access the remote application with:
twitter = oauth.create_client('twitter')
# or simply with
twitter = oauth.twitter
The configuration of those parameters can be loaded from the framework configuration. Each framework has its own config system, read the framework specified documentation later.
For instance, if client_id
and client_secret
can be loaded via
configuration, we can simply register the remote app with:
oauth.register(
name='twitter',
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
authorize_url='https://api.twitter.com/oauth/authenticate',
api_base_url='https://api.twitter.com/1.1/',
)
The client_kwargs
is a dict configuration to pass extra parameters to
OAuth 1 Session. If you are using RSA-SHA1
signature method:
client_kwargs = {
'signature_method': 'RSA-SHA1',
'signature_type': 'HEADER',
'rsa_key': 'Your-RSA-Key'
}
Usually, the framework integration has already implemented this part through the framework session system. All you need to do is enable session for the chosen framework.
For instance, GitHub is an OAuth 2.0 service, you want your users to log in your website with GitHub.
The first step is register a remote application on the OAuth
registry via
oauth.register
method:
oauth.register(
name='github',
client_id='{{ your-github-client-id }}',
client_secret='{{ your-github-client-secret }}',
access_token_url='https://github.com/login/oauth/access_token',
access_token_params=None,
authorize_url='https://github.com/login/oauth/authorize',
authorize_params=None,
api_base_url='https://api.github.com/',
client_kwargs={'scope': 'user:email'},
)
The first parameter in register
method is the name of the remote
application. You can access the remote application with:
github = oauth.create_client('github')
# or simply with
github = oauth.github
The configuration of those parameters can be loaded from the framework configuration. Each framework has its own config system, read the framework specified documentation later.
The client_kwargs
is a dict configuration to pass extra parameters to
OAuth 2 Session, you can pass extra parameters like:
client_kwargs = {
'scope': 'profile',
'token_endpoint_auth_method': 'client_secret_basic',
'token_placement': 'header',
}
There are several token_endpoint_auth_method
, get a deep inside the
Client Authentication Methods.
Note
Authlib is using request_token_url
to detect if the client is an
OAuth 1.0 or OAuth 2.0 client. In OAuth 2.0, there is no request_token_url
.
After configuring the OAuth
registry and the remote application, the
rest steps are much simpler. The only required parts are routes:
redirect to 3rd party provider (GitHub) for authentication
redirect back to your website to fetch access token and profile
Here is the example for GitHub login:
def login(request):
github = oauth.create_client('github')
redirect_uri = 'https://example.com/authorize'
return github.authorize_redirect(request, redirect_uri)
def authorize(request):
token = oauth.github.authorize_access_token(request)
resp = oauth.github.get('user', token=token)
resp.raise_for_status()
profile = resp.json()
# do something with the token and profile
return '...'
After user confirmed on GitHub authorization page, it will redirect
back to your website authorize
. In this route, you can get your
user’s GitHub profile information, you can store the user information
in your database, mark your user as logged in and etc.
Note
You may find that our documentation for OAuth 1.0 and OAuth 2.0 are the same. They are designed to share the same API, so that you use the same code for both OAuth 1.0 and OAuth 2.0.
The ONLY difference is the configuration. OAuth 1.0 contains
request_token_url
and request_token_params
while OAuth 2.0
not. Also, the client_kwargs
are different.
When fetching access token, the authorization server will require a client authentication, Authlib provides three default methods defined by RFC7591:
client_secret_basic
client_secret_post
none
But if the remote provider does not support these three methods, we need to register our own authentication methods, like Client Authentication:
from authlib.oauth2.rfc7523 import ClientSecretJWT
oauth.register(
'name',
...
client_auth_methods=[
ClientSecretJWT(token_endpoint), # client_secret_jwt
]
)
New in version v0.15: Starting from v0.15, developers can add custom authentication methods directly to token endpoint:
oauth.register(
'name',
...
token_endpoint_auth_method=ClientSecretJWT(token_endpoint),
)
Note
If your application ONLY needs login via 3rd party services like Twitter, Google, Facebook and GitHub to login, you DON’T need to create the token database.
There are also chances that you need to access your user’s 3rd party OAuth provider resources. For instance, you want to display the logged in user’s twitter time line and GitHub repositories. You will use access token to fetch the resources:
def get_twitter_tweets(request):
token = OAuth1Token.find(
name='twitter',
user=request.user
)
# API URL: https://api.twitter.com/1.1/statuses/user_timeline.json
resp = oauth.twitter.get('statuses/user_timeline.json', token=token.to_token())
resp.raise_for_status()
return resp.json()
def get_github_repositories(request):
token = OAuth2Token.find(
name='github',
user=request.user
)
# API URL: https://api.github.com/user/repos
resp = oauth.github.get('user/repos', token=token.to_token())
resp.raise_for_status()
return resp.json()
In this case, we need a place to store the access token in order to use
it later. Usually we will save the token into database. In the previous
Routes for Authorization authorize
part, we can save the token into
database.
It is possible to share one database table for both OAuth 1.0 token and OAuth 2.0 token. It is also good to use different database tables for OAuth 1.0 and OAuth 2.0.
In the above example, we are using two tables. Here are some hints on how to design the database:
class OAuth1Token(Model):
name = String(length=40)
oauth_token = String(length=200)
oauth_token_secret = String(length=200)
user = ForeignKey(User)
def to_token(self):
return dict(
oauth_token=self.access_token,
oauth_token_secret=self.alt_token,
)
class OAuth2Token(Model):
name = String(length=40)
token_type = String(length=40)
access_token = String(length=200)
refresh_token = String(length=200)
expires_at = PositiveIntegerField()
user = ForeignKey(User)
def to_token(self):
return dict(
access_token=self.access_token,
token_type=self.token_type,
refresh_token=self.refresh_token,
expires_at=self.expires_at,
)
And then we can save user’s access token into database when user was redirected
back to our authorize
page.
You can always pass a token
parameter to the remote application request
methods, like:
token = OAuth1Token.find(name='twitter', user=request.user)
oauth.twitter.get(url, token=token)
oauth.twitter.post(url, token=token)
oauth.twitter.put(url, token=token)
oauth.twitter.delete(url, token=token)
token = OAuth2Token.find(name='github', user=request.user)
oauth.github.get(url, token=token)
oauth.github.post(url, token=token)
oauth.github.put(url, token=token)
oauth.github.delete(url, token=token)
However, it is not a good practice to query the token database in every request
function. Authlib provides a way to fetch current user’s token automatically for
you, just register
with fetch_token
function:
def fetch_twitter_token(request):
token = OAuth1Token.find(
name='twitter',
user=request.user
)
return token.to_token()
def fetch_github_token(request):
token = OAuth2Token.find(
name='github',
user=request.user
)
return token.to_token()
# we can registry this ``fetch_token`` with oauth.register
oauth.register(
'twitter',
# ...
fetch_token=fetch_twitter_token,
)
oauth.register(
'github',
# ...
fetch_token=fetch_github_token,
)
Not good enough. In this way, you have to write fetch_token
for every
remote application. There is also a shared way to fetch token:
def fetch_token(name, request):
if name in OAUTH1_SERVICES:
model = OAuth1Token
else:
model = OAuth2Token
token = model.find(
name=name,
user=request.user
)
return token.to_token()
# initialize OAuth registry with this fetch_token function
oauth = OAuth(fetch_token=fetch_token)
Now, developers don’t have to pass a token
in the HTTP requests,
instead, they can pass the request
:
def get_twitter_tweets(request):
resp = oauth.twitter.get('statuses/user_timeline.json', request=request)
resp.raise_for_status()
return resp.json()
Note
Flask is different, you don’t need to pass the request
either.
OAuth 1.0 is a protocol, while OAuth 2.0 is a framework. There are so many features in OAuth 2.0 than OAuth 1.0. This section is designed for OAuth 2.0 specially.
In OAuth 1.0, access token never expires. But in OAuth 2.0, token MAY expire. If
there is a refresh_token
value, Authlib will auto update the access token if
it is expired.
We do this by passing a update_token
function to OAuth
registry:
def update_token(name, token, refresh_token=None, access_token=None):
if refresh_token:
item = OAuth2Token.find(name=name, refresh_token=refresh_token)
elif access_token:
item = OAuth2Token.find(name=name, access_token=access_token)
else:
return
# update old token
item.access_token = token['access_token']
item.refresh_token = token.get('refresh_token')
item.expires_at = token['expires_at']
item.save()
oauth = OAuth(update_token=update_token)
In this way, OAuth 2.0 integration will update expired token automatically. There is also a signal way to update token. Checkout the frameworks documentation.
Adding code_challenge
provided by RFC7636: Proof Key for Code Exchange by OAuth Public Clients is simple. You
register your remote app with a code_challenge_method
in client_kwargs
:
oauth.register(
'example',
client_id='Example Client ID',
client_secret='Example Client Secret',
access_token_url='https://example.com/oauth/access_token',
authorize_url='https://example.com/oauth/authorize',
api_base_url='https://api.example.com/',
client_kwargs={'code_challenge_method': 'S256'},
)
Note, the only supported code_challenge_method
is S256
.
For non standard OAuth 2.0 service, you can pass a compliance_fix
when
.register
. For example, Slack has a compliance problem, we can construct
a method to fix the requests session:
def slack_compliance_fix(session):
def _fix(resp):
resp.raise_for_status()
token = resp.json()
# slack returns no token_type
token['token_type'] = 'Bearer'
resp._content = to_unicode(json.dumps(token)).encode('utf-8')
return resp
session.register_compliance_hook('access_token_response', _fix)
Then pass this slack_compliance_fix
into .register
parameters:
oauth.register(
'slack',
client_id='...',
client_secret='...',
...,
compliance_fix=slack_compliance_fix,
...
)
Find all the available compliance hooks at Compliance Fix for non Standard.
When logging in with OpenID Connect, “access_token” is not what developers
want. Instead, what developers want is user info, Authlib wrap it with
UserInfo
.
There are two ways to fetch userinfo from 3rd party providers. If the
provider supports OpenID Connect, we can get the user info from the returned
id_token
.
Passing a userinfo_endpoint
when .register
remote client:
oauth.register(
'google',
client_id='...',
client_secret='...',
userinfo_endpoint='https://openidconnect.googleapis.com/v1/userinfo',
)
And later, when the client has obtained the access token, we can call:
def authorize(request):
token = oauth.google.authorize_access_token(request)
user = oauth.google.userinfo(request)
return '...'
id_token
¶For OpenID Connect provider, when .authorize_access_token
, the provider
will include a id_token
in the response. This id_token
contains the
UserInfo
we need so that we don’t have to fetch userinfo endpoint again.
The id_token
is a JWT, with Authlib JSON Web Token (JWT), we can decode it
easily. Frameworks integrations will handle it automatically if configurations
are correct.
A simple solution is to provide the OpenID Connect Discovery Endpoint:
oauth.register(
'google',
client_id='...',
client_secret='...',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid email profile'},
)
The discovery endpoint provides all the information we need so that we don’t
have to add authorize_url
and access_token_url
.
Check out our client example: https://github.com/authlib/demo-oauth-client
But if there is no discovery endpoint, developers MUST add all the missing information themselves:
* authorize_url
* access_token_url
* jwks_uri
This jwks_uri
is the URL to get provider’s public JWKs. Developers MAY also
provide the value of jwks
instead of jwks_uri
:
oauth.register(
'google',
client_id='...',
client_secret='...',
access_token_url='https://example.com/oauth/access_token',
authorize_url='https://example.com/oauth/authorize',
jwks={"keys": [...]}
)
This documentation covers OAuth 1.0, OAuth 2.0 and OpenID Connect Client support for Flask. Looking for OAuth providers?
Flask OAuth client can handle OAuth 1 and OAuth 2 services. It shares a similar API with Flask-OAuthlib, you can transfer your code from Flask-OAuthlib to Authlib with ease.
Create a registry with OAuth
object:
from authlib.integrations.flask_client import OAuth
oauth = OAuth(app)
You can also initialize it later with init_app()
method:
oauth = OAuth()
oauth.init_app(app)
The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.
Important
Please read Web OAuth Clients at first. Authlib has a shared API design among framework integrations, learn them from Web OAuth Clients.
Authlib Flask OAuth registry can load the configuration from Flask app.config
automatically. Every key value pair in .register
can be omitted. They can be
configured in your Flask App configuration. Config keys are formatted as
{name}_{key}
in uppercase, e.g.
TWITTER_CLIENT_ID |
Twitter Consumer Key |
TWITTER_CLIENT_SECRET |
Twitter Consumer Secret |
TWITTER_REQUEST_TOKEN_URL |
URL to fetch OAuth request token |
If you register your remote app as oauth.register('example', ...)
, the
config keys would look like:
EXAMPLE_CLIENT_ID |
OAuth Consumer Key |
EXAMPLE_CLIENT_SECRET |
OAuth Consumer Secret |
EXAMPLE_ACCESS_TOKEN_URL |
URL to fetch OAuth access token |
Here is a full list of the configuration keys:
{name}_CLIENT_ID
: Client key of OAuth 1, or Client ID of OAuth 2
{name}_CLIENT_SECRET
: Client secret of OAuth 2, or Client Secret of OAuth 2
{name}_REQUEST_TOKEN_URL
: Request Token endpoint for OAuth 1
{name}_REQUEST_TOKEN_PARAMS
: Extra parameters for Request Token endpoint
{name}_ACCESS_TOKEN_URL
: Access Token endpoint for OAuth 1 and OAuth 2
{name}_ACCESS_TOKEN_PARAMS
: Extra parameters for Access Token endpoint
{name}_AUTHORIZE_URL
: Endpoint for user authorization of OAuth 1 ro OAuth 2
{name}_AUTHORIZE_PARAMS
: Extra parameters for Authorization Endpoint.
{name}_API_BASE_URL
: A base URL endpoint to make requests simple
{name}_CLIENT_KWARGS
: Extra keyword arguments for OAuth1Session or OAuth2Session
We suggest that you keep ONLY {name}_CLIENT_ID
and {name}_CLIENT_SECRET
in
your Flask application configuration.
By default, Flask OAuth registry will use Flask session to store OAuth 1.0 temporary credential (request token). However in this way, there are chances your temporary credential will be exposed.
Our OAuth
registry provides a simple way to store temporary credentials in a cache
system. When initializing OAuth
, you can pass an cache
instance:
oauth = OAuth(app, cache=cache)
# or initialize lazily
oauth = OAuth()
oauth.init_app(app, cache=cache)
A cache
instance MUST have methods:
.get(key)
.set(key, value, expires=None)
There is no request
in accessing OAuth resources either. Just like above,
we don’t need to pass request
parameter, everything is handled by Authlib
automatically:
from flask import render_template
@app.route('/github')
def show_github_profile():
resp = oauth.github.get('user')
resp.raise_for_status()
profile = resp.json()
return render_template('github.html', profile=profile)
In this case, our fetch_token
could look like:
from your_project import current_user
def fetch_token(name):
if name in OAUTH1_SERVICES:
model = OAuth1Token
else:
model = OAuth2Token
token = model.find(
name=name,
user=current_user,
)
return token.to_token()
# initialize OAuth registry with this fetch_token function
oauth = OAuth(fetch_token=fetch_token)
You don’t have to pass token
, you don’t have to pass request
. That
is the fantasy of Flask.
New in version v0.13: The signal is added since v0.13
Instead of define a update_token
method and passing it into OAuth registry,
it is also possible to use signal to listen for token updating.
Before using signal, make sure you have installed blinker library:
$ pip install blinker
Connect the token_update
signal:
from authlib.integrations.flask_client import token_update
@token_update.connect_via(app)
def on_token_update(sender, name, token, refresh_token=None, access_token=None):
if refresh_token:
item = OAuth2Token.find(name=name, refresh_token=refresh_token)
elif access_token:
item = OAuth2Token.find(name=name, access_token=access_token)
else:
return
# update old token
item.access_token = token['access_token']
item.refresh_token = token.get('refresh_token')
item.expires_at = token['expires_at']
item.save()
An OpenID Connect client is no different than a normal OAuth 2.0 client. When
register with openid
scope, the built-in Flask OAuth client will handle everything
automatically:
oauth.register(
'google',
...
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid profile email'}
)
When we get the returned token:
token = oauth.google.authorize_access_token()
There should be a id_token
in the response. Authlib has called .parse_id_token
automatically, we can get userinfo
in the token
:
userinfo = token['userinfo']
Here are some example projects for you to learn Flask OAuth client integrations:
OAuth 1.0: Flask Twitter Login.
OAuth 2.0 & OpenID Connect: Flask Google Login.
Looking for OAuth providers?
The Django client can handle OAuth 1 and OAuth 2 services. Authlib has a shared API design among framework integrations. Get started with Web OAuth Clients.
Create a registry with OAuth
object:
from authlib.integrations.django_client import OAuth
oauth = OAuth()
The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.
Important
Please read Web OAuth Clients at first. Authlib has a shared API design among framework integrations, learn them from Web OAuth Clients.
Authlib Django OAuth registry can load the configuration from your Django application settings automatically. Every key value pair can be omitted. They can be configured from your Django settings:
AUTHLIB_OAUTH_CLIENTS = {
'twitter': {
'client_id': 'Twitter Consumer Key',
'client_secret': 'Twitter Consumer Secret',
'request_token_url': 'https://api.twitter.com/oauth/request_token',
'request_token_params': None,
'access_token_url': 'https://api.twitter.com/oauth/access_token',
'access_token_params': None,
'refresh_token_url': None,
'authorize_url': 'https://api.twitter.com/oauth/authenticate',
'api_base_url': 'https://api.twitter.com/1.1/',
'client_kwargs': None
}
}
There are differences between OAuth 1.0 and OAuth 2.0, please check the parameters
in .register
in Web OAuth Clients.
In OAuth 1.0, we need to use a temporary credential to exchange access token, this temporary credential was created before redirecting to the provider (Twitter), we need to save this temporary credential somewhere in order to use it later.
In OAuth 1, Django client will save the request token in sessions. In this case, you just need to configure Session Middleware in Django:
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware'
]
Follow the official Django documentation to set a proper session. Either a database backend or a cache backend would work well.
Warning
Be aware, using secure cookie as session backend will expose your request token.
Instead of defining an update_token
method and passing it into OAuth registry,
it is also possible to use signals to listen for token updates:
from django.dispatch import receiver
from authlib.integrations.django_client import token_update
@receiver(token_update)
def on_token_update(sender, name, token, refresh_token=None, access_token=None, **kwargs):
if refresh_token:
item = OAuth2Token.find(name=name, refresh_token=refresh_token)
elif access_token:
item = OAuth2Token.find(name=name, access_token=access_token)
else:
return
# update old token
item.access_token = token['access_token']
item.refresh_token = token.get('refresh_token')
item.expires_at = token['expires_at']
item.save()
An OpenID Connect client is no different than a normal OAuth 2.0 client. When
registered with the openid
scope, the built-in Django OAuth client will handle
everything automatically:
oauth.register(
'google',
...
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid profile email'}
)
When we get the returned token:
token = oauth.google.authorize_access_token(request)
There should be a id_token
in the response. Authlib has called .parse_id_token
automatically, we can get userinfo
in the token
:
userinfo = token['userinfo']
Find Django Google login example at https://github.com/authlib/demo-oauth-client/tree/master/django-google-login
Starlette is a lightweight ASGI framework/toolkit, which is ideal for building high performance asyncio services.
This documentation covers OAuth 1.0, OAuth 2.0 and OpenID Connect Client support for Starlette. Because all the frameworks integrations share the same API, it is best to:
Read Web OAuth Clients at first.
The difference between Starlette and Flask/Django integrations is Starlette
is async. We will use await
for the functions we need to call. But
first, let’s create an OAuth
instance:
from authlib.integrations.starlette_client import OAuth
oauth = OAuth()
The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.
Starlette can load configuration from environment; Authlib implementation for Starlette client can use this configuration. Here is an example of how to do it:
from starlette.config import Config
config = Config('.env')
oauth = OAuth(config)
Authlib will load client_id
and client_secret
from the configuration,
take google as an example:
oauth.register(name='google', ...)
It will load GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET from the environment.
oauth.register
is the same as Web OAuth Clients:
oauth.register(
'google',
client_id='...',
client_secret='...',
...
)
However, unlike Flask/Django, Starlette OAuth registry is using HTTPX
AsyncOAuth1Client
and
AsyncOAuth2Client
as the OAuth
backends. While Flask and Django are using the Requests version of
OAuth1Session
and
OAuth2Session
.
With OAuth 1.0, we need to use a temporary credential to exchange for an access token. This temporary credential is created before redirecting to the provider (Twitter), and needs to be saved somewhere in order to use it later.
With OAuth 1, the Starlette client will save the request token in sessions. To
enable this, we need to add the SessionMiddleware
middleware to the
application, which requires the installation of the itsdangerous
package:
from starlette.applications import Starlette
from starlette.middleware.sessions import SessionMiddleware
app = Starlette()
app.add_middleware(SessionMiddleware, secret_key="some-random-string")
However, using the SessionMiddleware
will store the temporary credential as
a secure cookie which will expose your request token to the client.
An OpenID Connect client is no different than a normal OAuth 2.0 client, just add
openid
scope when .register
. The built-in Starlette OAuth client will handle
everything automatically:
oauth.register(
'google',
...
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid profile email'}
)
When we get the returned token:
token = await oauth.google.authorize_access_token()
There should be a id_token
in the response. Authlib has called .parse_id_token
automatically, we can get userinfo
in the token
:
userinfo = token['userinfo']
We have Starlette demos at https://github.com/authlib/demo-oauth-client
OAuth 1.0: Starlette Twitter login
OAuth 2.0: Starlette Google login
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. It is build on top of Starlette, that means most of the code looks similar with Starlette code. You should first read documentation of:
Here is how you would create a FastAPI application:
from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
# we need this to save temporary code & state in session
app.add_middleware(SessionMiddleware, secret_key="some-random-string")
Since Authlib starlette requires using request
instance, we need to
expose that request
to Authlib. According to the documentation on
Using the Request Directly:
from starlette.requests import Request
@app.get("/login/google")
async def login_via_google(request: Request):
redirect_uri = request.url_for('auth_via_google')
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.get("/auth/google")
async def auth_via_google(request: Request):
token = await oauth.google.authorize_access_token(request)
user = token['userinfo']
return dict(user)
All other APIs are the same with Starlette.
We have a blog post about how to create Twitter login in FastAPI:
We have a blog post about how to create Google login in FastAPI:
This part of the documentation covers the interface of Authlib Client.
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.
url – The authorization endpoint URL.
request_token – The previously obtained request token.
kwargs – Optional parameters to append to the URL.
The authorization URL with new parameters embedded.
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.
url – Access Token endpoint.
verifier – A verifier string to prove authorization was granted.
kwargs – Extra parameters to include for fetching access token.
A token dict.
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.
url – Request Token endpoint.
realm – A string/list/tuple of realm for Authorization header.
kwargs – Extra parameters to include for fetching token.
A Request Token dict.
Note, realm
can also be configured when session created:
session = OAuth1Session(client_id, client_secret, ..., realm='')
Extract parameters from the post authorization redirect response URL.
url – The full URL that resulted from the user being redirected back from the OAuth provider to you, the client.
A dict of parameters extracted from the URL.
Signs the request using OAuth 1 (RFC5849)
Construct a new OAuth 2 client requests session.
client_id – Client ID, which you get from client registration.
client_secret – Client Secret, which you get from registration.
authorization_endpoint – URL of the authorization server’s authorization endpoint.
token_endpoint – URL of the authorization server’s token endpoint.
token_endpoint_auth_method – client authentication method for token endpoint.
revocation_endpoint – URL of the authorization server’s OAuth 2.0 revocation endpoint.
revocation_endpoint_auth_method – client authentication method for revocation endpoint.
scope – Scope that you needed to access user resources.
state – Shared secret to prevent CSRF attack.
redirect_uri – Redirect URI you registered as callback.
token – A dict of token attributes such as access_token
,
token_type
and expires_at
.
token_placement – The place to put token in HTTP request. Available values: “header”, “body”, “uri”.
update_token – A function for you to update token. It accept a
OAuth2Token
as parameter.
Generate an authorization URL and state.
url – Authorization endpoint url, must be HTTPS.
state – An optional state string for CSRF protection. If not given it will be generated for you.
code_verifier – An optional code_verifier for code challenge.
kwargs – Extra parameters to include.
authorization_url, state
Generic method for fetching an access token from the token endpoint.
url – Access Token endpoint URL, if not configured,
authorization_response
is used to extract token from
its fragment (implicit way).
body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.
method – The HTTP method used to make the request. Defaults to POST, but may also be GET. Other methods should be added as needed.
headers – Dict to default request headers with.
auth – An auth tuple or method as accepted by requests.
grant_type – Use specified grant_type to fetch token
A OAuth2Token
object (a dict too).
Implementation of OAuth 2.0 Token Introspection defined via RFC7662.
url – Introspection Endpoint, must be HTTPS.
token – The token to be introspected.
token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.
body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.
auth – An auth tuple or method as accepted by requests.
headers – Dict to default request headers with.
Introspection Response
Fetch a new access token using a refresh token.
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.
A OAuth2Token
object (a dict too).
Extend client authenticate for token endpoint.
auth – an instance to sign the request
Register a hook for request/response tweaking.
Available hooks are:
access_token_response: invoked before token parsing.
refresh_token_request: invoked before refreshing token.
refresh_token_response: invoked before refresh token parsing.
protected_request: invoked before making a request.
revoke_token_request: invoked before revoking a token.
introspect_token_request: invoked before introspecting a token.
Revoke token method defined via RFC7009.
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.
Revocation Response
Sign requests for OAuth 2.0, currently only bearer token is supported.
Signs the httpx request using OAuth 1 (RFC5849)
Execute the authentication flow.
To dispatch a request, yield it:
`
yield request
`
The client will .send() the response back into the flow generator. You can access it like so:
`
response = yield request
`
A return (or reaching the end of the generator) will result in the client returning the last response obtained from the server.
You can dispatch as many requests as is necessary.
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.
url – The authorization endpoint URL.
request_token – The previously obtained request token.
kwargs – Optional parameters to append to the URL.
The authorization URL with new parameters embedded.
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.
url – Access Token endpoint.
verifier – A verifier string to prove authorization was granted.
kwargs – Extra parameters to include for fetching access token.
A token dict.
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.
url – Request Token endpoint.
realm – A string/list/tuple of realm for Authorization header.
kwargs – Extra parameters to include for fetching token.
A Request Token dict.
Note, realm
can also be configured when session created:
session = OAuth1Session(client_id, client_secret, ..., realm='')
Extract parameters from the post authorization redirect response URL.
url – The full URL that resulted from the user being redirected back from the OAuth provider to you, the client.
A dict of parameters extracted from the URL.
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.
url – The authorization endpoint URL.
request_token – The previously obtained request token.
kwargs – Optional parameters to append to the URL.
The authorization URL with new parameters embedded.
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.
url – Access Token endpoint.
verifier – A verifier string to prove authorization was granted.
kwargs – Extra parameters to include for fetching access token.
A token dict.
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.
url – Request Token endpoint.
realm – A string/list/tuple of realm for Authorization header.
kwargs – Extra parameters to include for fetching token.
A Request Token dict.
Note, realm
can also be configured when session created:
session = OAuth1Session(client_id, client_secret, ..., realm='')
Extract parameters from the post authorization redirect response URL.
url – The full URL that resulted from the user being redirected back from the OAuth provider to you, the client.
A dict of parameters extracted from the URL.
Sign requests for OAuth 2.0, currently only bearer token is supported.
Generate an authorization URL and state.
url – Authorization endpoint url, must be HTTPS.
state – An optional state string for CSRF protection. If not given it will be generated for you.
code_verifier – An optional code_verifier for code challenge.
kwargs – Extra parameters to include.
authorization_url, state
Generic method for fetching an access token from the token endpoint.
url – Access Token endpoint URL, if not configured,
authorization_response
is used to extract token from
its fragment (implicit way).
body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.
method – The HTTP method used to make the request. Defaults to POST, but may also be GET. Other methods should be added as needed.
headers – Dict to default request headers with.
auth – An auth tuple or method as accepted by requests.
grant_type – Use specified grant_type to fetch token
A OAuth2Token
object (a dict too).
Implementation of OAuth 2.0 Token Introspection defined via RFC7662.
url – Introspection Endpoint, must be HTTPS.
token – The token to be introspected.
token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.
body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.
auth – An auth tuple or method as accepted by requests.
headers – Dict to default request headers with.
Introspection Response
Fetch a new access token using a refresh token.
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.
A OAuth2Token
object (a dict too).
Extend client authenticate for token endpoint.
auth – an instance to sign the request
Register a hook for request/response tweaking.
Available hooks are:
access_token_response: invoked before token parsing.
refresh_token_request: invoked before refreshing token.
refresh_token_response: invoked before refresh token parsing.
protected_request: invoked before making a request.
revoke_token_request: invoked before revoking a token.
introspect_token_request: invoked before introspecting a token.
Revoke token method defined via RFC7009.
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.
Revocation Response
Generate an authorization URL and state.
url – Authorization endpoint url, must be HTTPS.
state – An optional state string for CSRF protection. If not given it will be generated for you.
code_verifier – An optional code_verifier for code challenge.
kwargs – Extra parameters to include.
authorization_url, state
Generic method for fetching an access token from the token endpoint.
url – Access Token endpoint URL, if not configured,
authorization_response
is used to extract token from
its fragment (implicit way).
body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.
method – The HTTP method used to make the request. Defaults to POST, but may also be GET. Other methods should be added as needed.
headers – Dict to default request headers with.
auth – An auth tuple or method as accepted by requests.
grant_type – Use specified grant_type to fetch token
A OAuth2Token
object (a dict too).
Implementation of OAuth 2.0 Token Introspection defined via RFC7662.
url – Introspection Endpoint, must be HTTPS.
token – The token to be introspected.
token_type_hint – The type of the token that to be revoked. It can be “access_token” or “refresh_token”.
body – Optional application/x-www-form-urlencoded body to add the include in the token request. Prefer kwargs over body.
auth – An auth tuple or method as accepted by requests.
headers – Dict to default request headers with.
Introspection Response
Fetch a new access token using a refresh token.
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.
A OAuth2Token
object (a dict too).
Extend client authenticate for token endpoint.
auth – an instance to sign the request
Register a hook for request/response tweaking.
Available hooks are:
access_token_response: invoked before token parsing.
refresh_token_request: invoked before refreshing token.
refresh_token_response: invoked before refresh token parsing.
protected_request: invoked before making a request.
revoke_token_request: invoked before revoking a token.
introspect_token_request: invoked before introspecting a token.
Revoke token method defined via RFC7009.
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.
Revocation Response
Create or get the given named OAuth client. For instance, the
OAuth registry has .register
a twitter client, developers may
access the client with:
client = oauth.create_client('twitter')
name: Name of the remote application
OAuth remote app
Initialize lazy for Flask app. This is usually used for Flask application factory pattern.
Registers a new remote application.
name – Name of the remote application.
overwrite – Overwrite existing config with framework settings.
kwargs – Parameters for RemoteApp
.
Find parameters for the given remote app class. When a remote app is registered, it can be accessed with named attribute:
oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
Create or get the given named OAuth client. For instance, the
OAuth registry has .register
a twitter client, developers may
access the client with:
client = oauth.create_client('twitter')
name: Name of the remote application
OAuth remote app
Registers a new remote application.
name – Name of the remote application.
overwrite – Overwrite existing config with framework settings.
kwargs – Parameters for RemoteApp
.
Find parameters for the given remote app class. When a remote app is registered, it can be accessed with named attribute:
oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
Create or get the given named OAuth client. For instance, the
OAuth registry has .register
a twitter client, developers may
access the client with:
client = oauth.create_client('twitter')
name: Name of the remote application
OAuth remote app
Registers a new remote application.
name – Name of the remote application.
overwrite – Overwrite existing config with framework settings.
kwargs – Parameters for RemoteApp
.
Find parameters for the given remote app class. When a remote app is registered, it can be accessed with named attribute:
oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
This part of the documentation contains information on the JOSE implementation. It includes:
JSON Web Signature (JWS)
JSON Web Encryption (JWE)
JSON Web Key (JWK)
JSON Web Algorithm (JWA)
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) represents content secured with digital signatures or Message Authentication Codes (MACs) using JSON-based data structures.
There are two types of JWS Serializations:
JWS Compact Serialization
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:
General JWS JSON Serialization Syntax
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.
Generate a JWS compact serialization would be easy with
JsonWebSignature.serialize_compact()
, build a JWS instance with JWA:
from authlib.jose import JsonWebSignature
jws = JsonWebSignature()
# alg is a required parameter name
protected = {'alg': 'HS256'}
payload = b'example'
secret = b'secret'
jws.serialize_compact(protected, payload, secret)
There are other alg
that you could use. Here is a full list of available
algorithms:
HS256, HS384, HS512
RS256, RS384, RS512
ES256, ES384, ES512, ES256K
PS256, PS384, PS512
EdDSA
For example, a JWS with RS256 requires a private PEM key to sign the JWS:
jws = JsonWebSignature(algorithms=['RS256'])
protected = {'alg': 'RS256'}
payload = b'example'
with open('private.pem', 'rb') as f:
secret = f.read()
jws.serialize_compact(protected, payload, secret)
To deserialize a JWS Compact Serialization, use
JsonWebSignature.deserialize_compact()
:
# if it is a RS256, we use public RSA key
with open('public.pem', 'rb') as f:
key = f.read()
data = jws.deserialize_compact(s, key)
jws_header = data['header']
payload = data['payload']
Important
The above method is susceptible to a signature bypass described in CVE-2016-10555. It allows mixing symmetric algorithms and asymmetric algorithms. You should never combine symmetric (HS) and asymmetric (RS, ES, PS) signature schemes.
If you must support both protocols use a custom key loader which provides a different keys for different methods.
Load a different key
for symmetric and asymmetric signatures:
def load_key(header, payload):
if header['alg'] == 'RS256':
return rsa_pub_key
elif header['alg'] == 'HS256':
return shared_secret
else:
raise UnsupportedAlgorithmError()
claims = jws.deserialize_compact(token, load_key)
A key
can be dynamically loaded, if you don’t know which key to be used:
def load_key(header, payload):
kid = header['kid']
return get_key_by_kid(kid)
jws.deserialize_compact(s, load_key)
The result of the deserialize_compact
is a dict, which contains header
and payload
. The value of the header
is a JWSHeader
.
Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).
JsonWebSignature.serialize_json()
is used to generate a JWS JSON Serialization,
JsonWebSignature.deserialize_json()
is used to extract a JWS JSON Serialization.
The usage is the same as “Compact Serialize and Deserialize”, the only difference is
the “header”:
# Flattened JSON serialization header syntax
header = {'protected': {'alg': 'HS256'}, 'header': {'cty': 'JWT'}}
key = b'secret'
payload = b'example'
jws.serialize_json(header, payload, key)
# General JSON serialization header syntax
header = [{'protected': {'alg': 'HS256'}, 'header': {'cty': 'JWT'}}]
jws.serialize_json(header, payload, key)
For general JSON Serialization, there may be many signatures, each signature can use its own key, in this case the dynamical key would be useful:
def load_private_key(header, payload):
kid = header['kid']
return get_private_key(kid)
header = [
{'protected': {'alg': 'HS256'}, 'header': {'kid': 'foo'}},
{'protected': {'alg': 'RS256'}, 'header': {'kid': 'bar'}},
]
data = jws.serialize_json(header, payload, load_private_key)
# data is a dict
def load_public_key(header, payload):
kid = header['kid']
return get_public_key(kid)
jws.deserialize_json(data, load_public_key)
Actually, there is a JsonWebSignature.serialize()
and
JsonWebSignature.deserialize()
, which can automatically serialize
and deserialize Compact and JSON Serializations.
The result of the deserialize_json
is a dict, which contains header
and payload
. The value of the header
is a JWSHeader
.
Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).
JsonWebSignature
has a validation on header parameter names. It will
first check if the parameter name is in “Registered Header Parameter Names”
defined by RFC7515 Section 4.1. Then it will check if the parameter name is
in your defined private headers.
In this case, if there are header parameter names out of the registered header parameter names scope, you can pass the names:
private_headers = ['h1', 'h2']
jws = JsonWebSignature(private_headers=private_headers)
JSON Web Encryption (JWE) represents encrypted content using JSON-based data structures.
There are two types of JWE Serializations:
JWE Compact Serialization
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.
Generate a JWE compact serialization would be easy with
JsonWebEncryption.serialize_compact()
, build a JWE instance with JWA:
from authlib.jose import JsonWebEncryption
jwe = JsonWebEncryption()
protected = {'alg': 'RSA-OAEP', 'enc': 'A256GCM'}
payload = b'hello'
with open('rsa_public.pem', 'rb') as f:
key = f.read()
s = jwe.serialize_compact(protected, payload, key)
There are two required algorithms in protected header: alg
and enc
.
The available alg
list:
RSA1_5, RSA-OAEP, RSA-OAEP-256
A128KW, A192KW, A256KW
A128GCMKW, A192GCMKW, A256GCMKW
The available enc
list:
A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
A128GCM, A192GCM, A256GCM
More alg
and enc
will be added in the future.
It is also available to compress the payload with zip
header:
protected = {'alg': 'RSA-OAEP', 'enc': 'A256GCM', 'zip': 'DEF'}
s = jwe.serialize_compact(protected, payload, key)
To deserialize a JWE Compact Serialization, use
JsonWebEncryption.deserialize_compact()
:
with open('rsa_private.pem', 'rb') as f:
key = f.read()
data = jwe.deserialize_compact(s, key)
jwe_header = data['header']
payload = data['payload']
The result of the deserialize_compact
is a dict, which contains header
and payload
.
Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).
Changed in version v0.15: This documentation is updated for v0.15. Please check “v0.14” documentation for Authlib v0.14.
A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. An example would help a lot:
{
"kty": "EC",
"crv": "P-256",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"kid": "iss-a"
}
This is an Elliptic Curve Public Key represented by JSON data structure.
JsonWebKey.import_key()
will convert PEM, JSON, bytes into these keys:
Algorithms for kty
(Key Type) is defined by RFC7518: JSON Web Algorithms.
Import a key with:
from authlib.jose import JsonWebKey
key_data = read_file('public.pem')
key = JsonWebKey.import_key(key_data, {'kty': 'RSA'})
key.as_dict()
key.as_json()
You may pass extra parameters into import_key
method, available parameters can
be found on RFC7517 Section 4.
JSON Web Token (JWT) is structured by RFC7515: JSON Web Signature or RFC7516: JSON Web Encryption with certain payload claims. The JWT implementation in Authlib has all built-in algorithms via RFC7518: JSON Web Algorithms, it can also load private/public keys of RFC7517: JSON Web Key:
>>> from authlib.jose import jwt
>>> header = {'alg': 'RS256'}
>>> payload = {'iss': 'Authlib', 'sub': '123', ...}
>>> key = read_file('private.pem')
>>> s = jwt.encode(header, payload, key)
>>> claims = jwt.decode(s, read_file('public.pem'))
>>> print(claims)
{'iss': 'Authlib', 'sub': '123', ...}
>>> print(claims.header)
{'alg': 'RS256', 'typ': 'JWT'}
>>> claims.validate()
The imported jwt
is an instance of JsonWebToken
. It has all
supported JWS algorithms, and it can handle JWK automatically. When
JsonWebToken.encode()
a payload, JWT will check payload claims for
security, if you really want to expose them, you can always turn it off
via check=False
.
Important
JWT payload with JWS is not encrypted, it is just signed. Anyone can extract the payload without any private or public keys. Adding sensitive data like passwords, social security numbers in JWT payload is not safe if you are going to send them in a non-secure connection.
You can also use JWT with JWE which is encrypted. But this feature is not mature, documentation is not provided yet.
jwt.encode
is the method to create a JSON Web Token string. It encodes the
payload with the given alg
in header:
>>> from authlib.jose import jwt
>>> header = {'alg': 'RS256'}
>>> payload = {'iss': 'Authlib', 'sub': '123', ...}
>>> key = read_file('private.pem')
>>> s = jwt.encode(header, payload, key)
The available keys in headers are defined by RFC7515: JSON Web Signature.
jwt.decode
is the method to translate a JSON Web Token string into the
dict of the payload:
>>> from authlib.jose import jwt
>>> claims = jwt.decode(s, read_file('public.pem'))
Important
This decoding method is insecure. By default jwt.decode
parses the alg header.
This allows symmetric macs and asymmetric signatures. If both are allowed a signature bypass described in CVE-2016-10555 is possible.
See the following section for a mitigation.
The returned value is a JWTClaims
, check the next section to
validate claims value.
There are cases that we don’t want to support all the alg
values,
especially when decoding a token. In this case, we can pass a list
of supported alg
into JsonWebToken
:
>>> from authlib.jose import JsonWebToken
>>> jwt = JsonWebToken(['RS256'])
Important
You should never combine symmetric (HS) and asymmetric (RS, ES, PS) signature schemes. When both are allowed a signature bypass described in CVE-2016-10555 is possible.
If you must support both protocols use a custom key loader which provides a different keys for different methods.
Load a different key
for symmetric and asymmetric signatures:
def load_key(header, payload):
if header['alg'] == 'RS256':
return rsa_pub_key
elif header['alg'] == 'HS256':
return shared_secret
else:
raise UnsupportedAlgorithmError()
claims = jwt.decode(token, load_key)
JsonWebToken.decode()
accepts 3 claims-related parameters: claims_cls
,
claims_option
and claims_params
. The default claims_cls
is
JWTClaims
. The decode
method returns:
>>> JWTClaims(payload, header, options=claims_options, params=claims_params)
Claims validation is actually handled by JWTClaims.validate()
, which
validates payload claims with claims_option
and claims_params
. For
standard JWTClaims, claims_params
value is not used, but it is used in
IDToken
.
Here is an example of claims_option
:
{
"iss": {
"essential": True,
"values": ["https://example.com", "https://example.org"]
},
"sub": {
"essential": True
"value": "248289761001"
},
"jti": {
"validate": validate_jti
}
}
It is a dict configuration, the option key is the name of a claim.
essential: this value is REQUIRED.
values: claim value can be any one in the values list.
value: claim value MUST be the same value.
validate: a function to validate the claim value.
When .encode
and .decode
a token, there is a key
parameter to use.
This key
can be the bytes of your PEM key, a JWK set, and a function.
There ara cases that you don’t know which key to use to .decode
the token.
For instance, you have a JWK set:
jwks = {
"keys": [
{ "kid": "k1", ...},
{ "kid": "k2", ...},
]
}
And in the token, it has a kid=k2
in the header part, if you pass jwks
to
the key
parameter, Authlib will auto resolve the correct key:
jwt.decode(s, key=jwks, ...)
It is also possible to resolve the correct key by yourself:
def resolve_key(header, payload):
return my_keys[header['kid']]
jwt.decode(s, key=resolve_key)
For .encode
, if you pass a JWK set, it will randomly pick a key and assign its
kid
into the header.
This section contains introduction and implementation of Authlib core OAuth 1.0, OAuth 2.0, and OpenID Connect.
OAuth 1.0 is the standardization and combined wisdom of many well established industry protocols at its creation time. It was first introduced as Twitter’s open protocol. It is similar to other protocols at that time in use (Google AuthSub, AOL OpenAuth, Yahoo BBAuth, Upcoming API, Flickr API, etc).
If you are creating an open platform, AUTHLIB ENCOURAGE YOU USE OAUTH 2.0 INSTEAD.
OAuth 1.0 is the standardization and combined wisdom of many well established industry protocols at its creation time. It was first introduced as Twitter’s open protocol. It is similar to other protocols at that time in use (Google AuthSub, AOL OpenAuth, Yahoo BBAuth, Upcoming API, Flickr API, etc).
Authlib implemented OAuth 1.0 according to RFC5849, this section will help developers understand the concepts in OAuth 1.0, the authorization flow of OAuth 1.0, and etc.
OAuth provides a method for clients to access server resources on behalf of a resource owner (such as a different client or an end- user). It also provides a process for end-users to authorize third- party access to their server resources without sharing their credentials (typically, a username and password pair), using user- agent redirection.
Here is an overview of a typical OAuth 1.0 authorization flow:
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:
Client uses its client credentials to make a request to server, asking the server for a temporary credential.
Server responds with a temporary credential if it verified your client credential.
Client saves temporary credential for later use, then open a web view (browser) for resource owner to grant the access.
When access is granted, Server responds with a verifier to client.
Client uses this verifier and temporary credential to make a request to the server asking for token credentials.
Server responds with access token if it verified everything.
And then Client can send tweets with the token credentials.
To understand above flow, you need to know the roles in OAuth 1.0. There are usually three roles in an OAuth 1.0 flow. Take the above example, imagining that you are building a mobile app to send tweets:
Client: a client is a third-party application. In this case, it is your Twitter application.
Resource Owner: the users on Twitter are the resource owners, since they own their tweets (resources).
Server: authorization and resource server. In this case, it is Twitter.
Let’s explain OAuth 1.0 in HTTP one more time. The first step is:
Client uses its client credentials to make a request to server, asking the server for a temporary credential.
It means we need to ask a temporary credential from Twitter. A temporary credential is called request token in Twitter. The first request is (line breaks are for display purposes only):
POST /oauth/request_token HTTP/1.1
Host: api.twitter.com
Authorization: OAuth
oauth_consumer_key="dpf43f3p2l4k3l03",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="137131200",
oauth_nonce="wIjqoS",
oauth_callback="https%3A%2F%.example.com%2Fauth",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D",
oauth_version="1.0"
And Twitter will response with a temporary credential like:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik
&oauth_token_secret=Kd75W4OQfb2oJTV0vzGzeXftVAwgMnEK9MumzYcM
&oauth_callback_confirmed=true
Our Twitter client will then redirect user to the authorization page:
https://api.twitter.com/oauth/authenticate?oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik
On this authorization page, if user granted access to your Twitter client, it will redirect back to your application page, e.g.:
https://example.com/auth?oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik&oauth_verifier=hfdp7dh39dks9884
And the final step is here, use the temporary credential to exchange access token:
POST /oauth/access_token HTTP/1.1
Host: api.twitter.com
Authorization: OAuth
oauth_consumer_key="dpf43f3p2l4k3l03",
oauth_token="Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="137131201",
oauth_nonce="walatlh",
oauth_verifier="hfdp7dh39dks9884",
oauth_signature=".....",
oauth_version="1.0"
If everything works well, Twitter would response with the final access token now:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
oauth_token=6253282-eWudHldSbIaelX7swmsiHImEL4KinwaGloHANdrY
&oauth_token_secret=2EEfA6BG5ly3sR3XjE0IBSnlQu4ZrUzPiYTmrkVU
&user_id=6253282
You can use the oauth_token
and oauth_token_secret
for later use.
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:
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.
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:
Your application (client) prompts the user to log in.
The user clicks the login button, your application will redirect to GitHub’s authorize page (Authorization Server).
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.
The Authorization Server issues an access token to your application. (This step can contain several sub-steps)
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.
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:
JWTBearerGrant
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
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.
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.
The above example only shows one endpoint, which is token endpoint. There are more endpoints in OAuth 2.0. For example:
OpenID Connect is an identity layer on top of the OAuth 2.0 framework.
(TBD)
This section is about the core part of OpenID Connect. Authlib implemented OpenID Connect Core 1.0 on top of OAuth 2.0. It enhanced OAuth 2.0 with:
OpenIDCode
extension for Authorization code flow
OpenIDImplicitGrant
grant type for implicit flow
OpenIDHybridGrant
grant type for hybrid flow
This section is about OpenID Provider Discovery. OpenID Providers have metadata describing their configuration. The endpoint is usually located at:
/.well-known/openid-configuration
The metadata is formatted in JSON. Here is an example of how it looks like:
HTTP/1.1 200 OK
Content-Type: application/json
{
"issuer":
"https://server.example.com",
"authorization_endpoint":
"https://server.example.com/connect/authorize",
"token_endpoint":
"https://server.example.com/connect/token",
"token_endpoint_auth_methods_supported":
["client_secret_basic", "private_key_jwt"],
"token_endpoint_auth_signing_alg_values_supported":
["RS256", "ES256"],
"userinfo_endpoint":
"https://server.example.com/connect/userinfo",
"check_session_iframe":
"https://server.example.com/connect/check_session",
"end_session_endpoint":
"https://server.example.com/connect/end_session",
"jwks_uri":
"https://server.example.com/jwks.json",
"registration_endpoint":
"https://server.example.com/connect/register",
"scopes_supported":
["openid", "profile", "email", "address",
"phone", "offline_access"],
"response_types_supported":
["code", "code id_token", "id_token", "token id_token"],
"acr_values_supported":
["urn:mace:incommon:iap:silver",
"urn:mace:incommon:iap:bronze"],
"subject_types_supported":
["public", "pairwise"],
"userinfo_signing_alg_values_supported":
["RS256", "ES256", "HS256"],
"userinfo_encryption_alg_values_supported":
["RSA1_5", "A128KW"],
"userinfo_encryption_enc_values_supported":
["A128CBC-HS256", "A128GCM"],
"id_token_signing_alg_values_supported":
["RS256", "ES256", "HS256"],
"id_token_encryption_alg_values_supported":
["RSA1_5", "A128KW"],
"id_token_encryption_enc_values_supported":
["A128CBC-HS256", "A128GCM"],
"request_object_signing_alg_values_supported":
["none", "RS256", "ES256"],
"display_values_supported":
["page", "popup"],
"claim_types_supported":
["normal", "distributed"],
"claims_supported":
["sub", "iss", "auth_time", "acr",
"name", "given_name", "family_name", "nickname",
"profile", "picture", "website",
"email", "email_verified", "locale", "zoneinfo",
"http://example.info/claims/groups"],
"claims_parameter_supported":
true,
"service_documentation":
"http://server.example.com/connect/service_documentation.html",
"ui_locales_supported":
["en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"]
}
Authlib has built-in Flask integrations for building OAuth 1.0, OAuth 2.0 and OpenID Connect servers. It is best if developers can read Introduce OAuth 1.0 and Introduce OAuth 2.0 at first.
Implement OAuth 1.0 provider in Flask. An OAuth 1 provider contains two servers:
Authorization Server: to issue access tokens
Resources Server: to serve your users’ resources
At the very beginning, we need to have some basic understanding of the OAuth 1.0.
Important
If you are developing on your localhost, remember to set the environment variable:
export AUTHLIB_INSECURE_TRANSPORT=true
Looking for Flask OAuth 1.0 client? Check out Flask OAuth Client.
Changed in version v1.0.0: We have removed built-in SQLAlchemy integrations.
Protect users resources, so that only the authorized clients with the authorized access token can access the given scope resources.
A resource server can be a different server other than the authorization server. Here is the way to protect your users’ resources:
from flask import jsonify
from authlib.integrations.flask_oauth1 import ResourceProtector, current_credential
# we will define ``query_client``, ``query_token``, and ``exists_nonce`` later.
require_oauth = ResourceProtector(
app, query_client=query_client,
query_token=query_token,
exists_nonce=exists_nonce,
)
# or initialize it lazily
require_oauth = ResourceProtector()
require_oauth.init_app(
app,
query_client=query_client,
query_token=query_token,
exists_nonce=exists_nonce,
)
@app.route('/user')
@require_oauth()
def user_profile():
user = current_credential.user
return jsonify(user)
The current_credential
is a proxy to the Token model you have defined above.
Since there is a user
relationship on the Token model, we can access this
user
with current_credential.user
.
To initialize ResourceProtector
, we need three functions:
query_client
query_token
exists_nonce
If using SQLAlchemy, the query_client
could be:
def query_client(client_id):
# assuming ``Client`` is the model
return Client.query.filter_by(client_id=client_id).first()
And query_token
would be:
def query_token(client_id, oauth_token):
return TokenCredential.query.filter_by(client_id=client_id, oauth_token=oauth_token).first()
For exists_nonce
, if you are using cache now (as in authorization server), Authlib
has a built-in tool function:
from authlib.integrations.flask_oauth1 import create_exists_nonce_func
exists_nonce = create_exists_nonce_func(cache)
If using database, with SQLAlchemy it would look like:
def exists_nonce(nonce, timestamp, client_id, oauth_token):
q = db.session.query(TimestampNonce.nonce).filter_by(
nonce=nonce,
timestamp=timestamp,
client_id=client_id,
)
if oauth_token:
q = q.filter_by(oauth_token=oauth_token)
rv = q.first()
if rv:
return True
tn = TimestampNonce(
nonce=nonce,
timestamp=timestamp,
client_id=client_id,
oauth_token=oauth_token,
)
db.session.add(tn)
db.session.commit()
return False
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()]
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.
This part of the documentation covers the interface of Flask OAuth 1.0 Server.
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)
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
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
request – OAuth1Request instance.
grant_user – if granted, pass the grant user, otherwise None.
(status_code, body, headers)
Create and bind oauth_verifier
to temporary credential. It
could be re-implemented in this way:
def create_authorization_verifier(self, request):
verifier = generate_token(36)
temporary_credential = request.credential
user_id = request.user.id
temporary_credential.user_id = user_id
temporary_credential.oauth_verifier = verifier
# if the credential has a save method
temporary_credential.save()
# remember to return the verifier
return verifier
request – OAuth1Request instance
A string of oauth_verifier
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
request – OAuth1Request instance
TemporaryCredential instance
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
request – OAuth1Request instance.
(status_code, body, headers)
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
request – OAuth1Request instance
TokenCredential instance
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
request – OAuth1Request instance.
(status_code, body, headers)
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)
request – OAuth1Request instance
The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
nonce – A string value of oauth_nonce
request – OAuth1Request instance
Boolean
Get client instance with the given client_id
.
client_id – A string of client_id
Client instance
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)
request – OAuth1Request instance
TemporaryCredential instance
A protecting method for resource servers. Initialize a resource protector with the these method:
query_client
query_token,
exists_nonce
Usually, a query_client
method would look like (if using SQLAlchemy):
def query_client(client_id):
return Client.query.filter_by(client_id=client_id).first()
A query_token
method accept two parameters, client_id
and oauth_token
:
def query_token(client_id, oauth_token):
return Token.query.filter_by(client_id=client_id, oauth_token=oauth_token).first()
And for exists_nonce
, if using cache, we have a built-in hook to create this method:
from authlib.integrations.flask_oauth1 import create_exists_nonce_func
exists_nonce = create_exists_nonce_func(cache)
Then initialize the resource protector with those methods:
require_oauth = ResourceProtector(
app, query_client=query_client,
query_token=query_token, exists_nonce=exists_nonce,
)
Get client instance with the given client_id
.
client_id – A string of client_id
Client instance
Fetch the token credential from data store like a database, framework should implement this function.
request – OAuth1Request instance
Token model instance
The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
nonce – A string value of oauth_nonce
request – OAuth1Request instance
Boolean
Routes protected by ResourceProtector
can access current credential
with this variable.
This section is not a step by step guide on how to create an OAuth 2.0 provider in Flask. Instead, we will learn how the Flask implementation works, and some technical details in an OAuth 2.0 provider.
If you need a quick example, here are the official tutorial guide and examples on GitHub:
At the very beginning, we need to have some basic understanding of the OAuth 2.0.
Important
If you are developing on your localhost, remember to set the environment variable:
export AUTHLIB_INSECURE_TRANSPORT=true
Looking for Flask OAuth 2.0 client? Check out Flask OAuth Client.
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.
The implicit grant type is usually used in a browser, when resource owner granted the access, an access token is issued in the redirect URI, there is no missing implementation, which means it can be easily registered with:
from authlib.oauth2.rfc6749 import grants
# register it to grant endpoint
server.register_grant(grants.ImplicitGrant)
Implicit Grant is used by public clients which have no client_secret.
Default allowed Client Authentication Methods: none
.
The resource owner uses its username and password to exchange an access
token. This grant type should be used only when the client is trustworthy;
implement it with a subclass of
ResourceOwnerPasswordCredentialsGrant
:
from authlib.oauth2.rfc6749 import grants
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
def authenticate_user(self, username, password):
user = User.query.filter_by(username=username).first()
if user.check_password(password):
return user
# register it to grant endpoint
server.register_grant(PasswordGrant)
Default allowed Client Authentication Methods: client_secret_basic
.
You can add more in the subclass:
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
Client credentials grant 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'
]
Many OAuth 2 providers do not implement a refresh token endpoint. Authlib
provides it as a grant type; implement it with a subclass of
RefreshTokenGrant
:
from authlib.oauth2.rfc6749 import grants
class RefreshTokenGrant(grants.RefreshTokenGrant):
def authenticate_refresh_token(self, refresh_token):
item = Token.query.filter_by(refresh_token=refresh_token).first()
# define is_refresh_token_valid by yourself
# usually, you should check if refresh token is expired and revoked
if item and item.is_refresh_token_valid():
return item
def authenticate_user(self, credential):
return User.query.get(credential.user_id)
def revoke_old_credential(self, credential):
credential.revoked = True
db.session.add(credential)
db.session.commit()
# register it to grant endpoint
server.register_grant(RefreshTokenGrant)
Default allowed Client Authentication Methods: client_secret_basic
.
You can add more in the subclass:
class RefreshTokenGrant(grants.RefreshTokenGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
By default, RefreshTokenGrant will not issue a refresh_token
in the token
response. Developers can change this behavior with:
class RefreshTokenGrant(grants.RefreshTokenGrant):
INCLUDE_NEW_REFRESH_TOKEN = True
It is also possible to create your own grant types. In Authlib, a Grant supports two endpoints:
Authorization Endpoint: which can handle requests with response_type
.
Token Endpoint: which is the endpoint to issue tokens.
Changed in version v0.12: Using AuthorizationEndpointMixin
and TokenEndpointMixin
instead of
AUTHORIZATION_ENDPOINT=True
and TOKEN_ENDPOINT=True
.
Creating a custom grant type with BaseGrant:
from authlib.oauth2.rfc6749.grants import (
BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin
)
class MyCustomGrant(BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin):
GRANT_TYPE = 'custom-grant-type-name'
def validate_authorization_request(self):
# only needed if using AuthorizationEndpointMixin
def create_authorization_response(self, grant_user):
# only needed if using AuthorizationEndpointMixin
def validate_token_request(self):
# only needed if using TokenEndpointMixin
def create_token_response(self):
# only needed if using TokenEndpointMixin
For a better understanding, you can read the source code of the built-in grant types. And there are extended grant types defined by other specs:
New in version 0.10.
Grants can accept extensions. Developers can pass extensions when registering grants:
authorization_server.register_grant(AuthorizationCodeGrant, [extension])
For instance, there is the CodeChallenge
extension in Authlib:
server.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=False)])
Learn more about CodeChallenge
at RFC7636: Proof Key for Code Exchange by OAuth Public Clients.
Flask OAuth 2.0 authorization server has a method to register other token
endpoints: authorization_server.register_endpoint
. Find the available
endpoints:
Protects users resources, so that only the authorized clients with the authorized access token can access the given scope resources.
A resource server can be a different server other than the authorization server. Authlib offers a decorator to protect your API endpoints:
from flask import jsonify
from authlib.integrations.flask_oauth2 import ResourceProtector, current_token
from authlib.oauth2.rfc6750 import BearerTokenValidator
class MyBearerTokenValidator(BearerTokenValidator):
def authenticate_token(self, token_string):
return Token.query.filter_by(access_token=token_string).first()
require_oauth = ResourceProtector()
# only bearer token is supported currently
require_oauth.register_token_validator(MyBearerTokenValidator())
When the resource server has no access to the Token
model (database), and
there is an introspection token endpoint in authorization server, you can
Use Introspection in Resource Server.
Here is the way to protect your users’ resources:
@app.route('/user')
@require_oauth('profile')
def user_profile():
# if Token model has `.user` foreign key
user = current_token.user
return jsonify(user)
If the resource is not protected by a scope, use None
:
@app.route('/user')
@require_oauth()
def user_profile():
user = current_token.user
return jsonify(user)
# or with None
@app.route('/user')
@require_oauth(None)
def user_profile():
user = current_token.user
return jsonify(user)
The current_token
is a proxy to the Token model you have defined above.
Since there is a user
relationship on the Token model, we can access this
user
with current_token.user
.
If the decorator is not your favorite, there is a with
statement for you:
@app.route('/user')
def user_profile():
with require_oauth.acquire('profile') as token:
user = token.user
return jsonify(user)
Changed in version v1.0.
You can apply multiple scopes to one endpoint in AND, OR and mix modes. Here are some examples:
@app.route('/profile')
@require_oauth(['profile email'])
def user_profile():
user = current_token.user
return jsonify(user)
It requires the token containing both profile
and email
scope.
@app.route('/profile')
@require_oauth(['profile', 'email']')
def user_profile():
user = current_token.user
return jsonify(user)
It requires the token containing either profile
or email
scope.
It is also possible to mix AND and OR logic. e.g.:
@app.route('/profile')
@require_oauth(['profile email', 'user'])
def user_profile():
user = current_token.user
return jsonify(user)
This means if the token will be valid if:
token contains both profile
and email
scope
or token contains user
scope
require_oauth
¶There is one more parameter for require_oauth
which is used to serve
public endpoints:
@app.route('/timeline')
@require_oauth(optional=True)
def timeline_api():
if current_token:
return get_user_timeline(current_token.user)
return get_public_timeline()
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')]
OpenID Connect 1.0 is supported since version 0.6. The integrations are built with Custom Grant Types and Grant Extensions. Since OpenID Connect is built on OAuth 2.0 frameworks, you need to read Flask OAuth 2.0 Server at first.
Changed in version v0.12: The Grant system has been redesigned from v0.12. This documentation ONLY works for Authlib >=v0.12.
Looking for OpenID Connect Client? Head over to Flask OAuth Client.
OpenID Connect 1.0 uses JWT a lot. Make sure you have the basic understanding of JOSE Guide.
For OpenID Connect, we need to understand at least four concepts:
alg: Algorithm for JWT
key: Private key for JWT
iss: Issuer value for JWT
exp: JWT expires time
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
.
A private key is required to generate a JWT. The key that you are going to use
dependents on the alg
you are using. For instance, the alg is RS256
,
you need to use an RSA private key. It can be set with:
key = '''-----BEGIN RSA PRIVATE KEY-----\nMIIEog...'''
# or in JWK format
key = {"kty": "RSA", "n": ...}
The iss
value in the JWT payload. The value can be your website name or
URL. For example, Google is using:
{"iss": "https://accounts.google.com"}
OpenID Connect authorization code flow relies on the OAuth2 authorization code flow and extends it.
OpenID Connect Code flow is the same as Authorization Code flow, but with
extended features. We can apply the OpenIDCode
extension to
Authorization Code Grant.
First, we need to implement the missing methods for OpenIDCode
:
from authlib.oidc.core import grants, UserInfo
class OpenIDCode(grants.OpenIDCode):
def exists_nonce(self, nonce, request):
exists = AuthorizationCode.query.filter_by(
client_id=request.client_id, nonce=nonce
).first()
return bool(exists)
def get_jwt_config(self, grant):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'https://example.com',
'exp': 3600
}
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
Second, since there is one more nonce
value in the AuthorizationCode
data, we need to save this value into the database. In this case, we have to
update our Authorization Code Grant save_authorization_code
method:
class AuthorizationCodeGrant(_AuthorizationCodeGrant):
def save_authorization_code(self, code, request):
# openid request MAY have "nonce" parameter
nonce = request.data.get('nonce')
auth_code = AuthorizationCode(
code=code,
client_id=request.client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=request.user.id,
nonce=nonce,
)
db.session.add(auth_code)
db.session.commit()
return auth_code
# ...
Finally, you can register AuthorizationCodeGrant
with the OpenIDCode
extension:
# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant, [OpenIDCode(require_nonce=True)])
The difference between OpenID Code flow and the standard code flow is that OpenID Connect requests have a scope of “openid”:
GET /authorize?
response_type=code
&scope=openid%20profile%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1
Host: server.example.com
With the example above, you will also have to change the scope of your client
in your application to something like openid profile email
.
Now that you added the openid
scope to your application, an OpenID token
will be provided to this app whenever a client asks for a token with an
openid
scope.
The Implicit Flow is mainly used by Clients implemented in a browser using
a scripting language. You need to implement the missing methods of
OpenIDImplicitGrant
before registering it:
from authlib.oidc.core import grants
class OpenIDImplicitGrant(grants.OpenIDImplicitGrant):
def exists_nonce(self, nonce, request):
exists = AuthorizationCode.query.filter_by(
client_id=request.client_id, nonce=nonce
).first()
return bool(exists)
def get_jwt_config(self):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'https://example.com',
'exp': 3600
}
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
server.register_grant(OpenIDImplicitGrant)
The Hybrid flow is a mix of code flow and implicit flow. You only need to implement the authorization endpoint part, as token endpoint will be handled by Authorization Code Flow.
OpenIDHybridGrant is a subclass of OpenIDImplicitGrant, so the missing methods
are the same, except that OpenIDHybridGrant has one more missing method, that
is save_authorization_code
. You can implement it like this:
from authlib.oidc.core import grants
from authlib.common.security import generate_token
class OpenIDHybridGrant(grants.OpenIDHybridGrant):
def save_authorization_code(self, code, request):
nonce = request.data.get('nonce')
item = AuthorizationCode(
code=code,
client_id=request.client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=request.user.id,
nonce=nonce,
)
db.session.add(item)
db.session.commit()
return code
def exists_nonce(self, nonce, request):
exists = AuthorizationCode.query.filter_by(
client_id=request.client_id, nonce=nonce
).first()
return bool(exists)
def get_jwt_config(self):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'https://example.com',
'exp': 3600
}
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
# register it to grant endpoint
server.register_grant(OpenIDHybridGrant)
Since all OpenID Connect Flow require exists_nonce
, get_jwt_config
and generate_user_info
methods, you can create shared functions for them.
Find the example of OpenID Connect server.
This part of the documentation covers the interface of Flask OAuth 2.0 Server.
Flask implementation of authlib.oauth2.rfc6749.AuthorizationServer
.
Initialize it with query_client
, save_token
methods and Flask
app instance:
def query_client(client_id):
return Client.query.filter_by(client_id=client_id).first()
def save_token(token, request):
if request.user:
user_id = request.user.id
else:
user_id = None
client = request.client
tok = Token(
client_id=client.client_id,
user_id=user.id,
**token
)
db.session.add(tok)
db.session.commit()
server = AuthorizationServer(app, query_client, save_token)
# or initialize lazily
server = AuthorizationServer()
server.init_app(app, query_client, save_token)
Validate authorization request and create authorization response.
request – HTTP request instance.
grant_user – if granted, it is resource owner. If denied, it is None.
Response
Create a generator function for generating token
value. This
method will create a Bearer Token generator with
authlib.oauth2.rfc6750.BearerToken
.
Configurable settings:
OAUTH2_ACCESS_TOKEN_GENERATOR: Boolean or import string, default is True.
OAUTH2_REFRESH_TOKEN_GENERATOR: Boolean or import string, default is False.
OAUTH2_TOKEN_EXPIRES_IN: Dict or import string, default is None.
By default, it will not generate refresh_token
, which can be turn on by
configure OAUTH2_REFRESH_TOKEN_GENERATOR
.
Here are some examples of the token generator:
OAUTH2_ACCESS_TOKEN_GENERATOR = 'your_project.generators.gen_token'
# and in module `your_project.generators`, you can define:
def gen_token(client, grant_type, user, scope):
# generate token according to these parameters
token = create_random_token()
return f'{client.id}-{user.id}-{token}'
Here is an example of OAUTH2_TOKEN_EXPIRES_IN
:
OAUTH2_TOKEN_EXPIRES_IN = {
'authorization_code': 864000,
'urn:ietf:params:oauth:grant-type:jwt-bearer': 3600,
}
Validate endpoint request and create endpoint response.
name – Endpoint name
request – HTTP request instance.
Response
Validate token request and create token response.
request – HTTP request instance
Validate current HTTP request for authorization page. This page is designed for resource owner to grant or deny the authorization.
Add extra endpoint to authorization server. e.g. RevocationEndpoint:
authorization_server.register_endpoint(RevocationEndpoint)
endpoint_cls – A endpoint class
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)
grant_cls – a grant class.
extensions – extensions for the grant class.
A protecting method for resource servers. Creating a require_oauth
decorator easily with ResourceProtector:
from authlib.integrations.flask_oauth2 import ResourceProtector
require_oauth = ResourceProtector()
# add bearer token validator
from authlib.oauth2.rfc6750 import BearerTokenValidator
from project.models import Token
class MyBearerTokenValidator(BearerTokenValidator):
def authenticate_token(self, token_string):
return Token.query.filter_by(access_token=token_string).first()
def request_invalid(self, request):
return False
def token_revoked(self, token):
return False
require_oauth.register_token_validator(MyBearerTokenValidator())
# protect resource with require_oauth
@app.route('/user')
@require_oauth(['profile'])
def user_profile():
user = User.query.get(current_token.user_id)
return jsonify(user.to_dict())
Raise HTTPException for OAuth2Error. Developers can re-implement this method to customize the error response.
error – OAuth2Error
HTTPException
A method to acquire current valid token with the given scope.
scopes – a list of scope values
token object
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())
Routes protected by ResourceProtector
can access current token
with this variable:
from authlib.integrations.flask_oauth2 import current_token
@require_oauth()
@app.route('/user_id')
def user_id():
# current token instance of the OAuth Token model
return current_token.user_id
Signal when client is authenticated
Signal when token is revoked
Signal when token is authenticated
Warning
We will drop sqla_oauth2
module in version 1.0.
Create an query_client
function that can be used in authorization
server.
session – SQLAlchemy session
client_model – Client model class
Create an save_token
function that can be used in authorization
server.
session – SQLAlchemy session
token_model – Token model class
Create an query_token
function for revocation, introspection
token endpoints.
session – SQLAlchemy session
token_model – Token model class
Create a revocation endpoint class with SQLAlchemy session and token model.
session – SQLAlchemy session
token_model – Token model class
Create an bearer token validator class with SQLAlchemy session and token model.
session – SQLAlchemy session
token_model – Token model class
Authlib has built-in Django integrations for building OAuth 1.0 and OAuth 2.0 servers. It is best if developers can read Introduce OAuth 1.0 and Introduce OAuth 2.0 at first.
This is just an alpha implementation of Django OAuth 1.0 provider. An OAuth 1 provider contains two servers:
Authorization Server: to issue access tokens
Resources Server: to serve your users’ resources
At the very beginning, we need to have some basic understanding of the OAuth 1.0.
Important
If you are developing on your localhost, remember to set the environment variable:
export AUTHLIB_INSECURE_TRANSPORT=true
Looking for Django OAuth 1.0 client? Check out Django OAuth Client.
Protect users resources, so that only the authorized clients with the authorized access token can access the given scope resources.
A resource server can be a different server other than the authorization server. Here is the way to protect your users’ resources:
from django.http import JsonResponse
from authlib.integrations.django_oauth1 import ResourceProtector
require_oauth = ResourceProtector(Client, TokenCredential)
@require_oauth()
def user_api(request):
user = request.oauth1_credential.user
return JsonResponse(dict(username=user.username))
The require_oauth
decorator will add a oauth1_credential
to request
parameter. This oauth1_credential
is an instance of the Token model.
This part of the documentation covers the interface of Django OAuth 1.0 Server.
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
request – OAuth1Request instance.
grant_user – if granted, pass the grant user, otherwise None.
(status_code, body, headers)
Create and bind oauth_verifier
to temporary credential. It
could be re-implemented in this way:
def create_authorization_verifier(self, request):
verifier = generate_token(36)
temporary_credential = request.credential
user_id = request.user.id
temporary_credential.user_id = user_id
temporary_credential.oauth_verifier = verifier
# if the credential has a save method
temporary_credential.save()
# remember to return the verifier
return verifier
request – OAuth1Request instance
A string of oauth_verifier
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
request – OAuth1Request instance
TemporaryCredential instance
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
request – OAuth1Request instance.
(status_code, body, headers)
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
request – OAuth1Request instance
TokenCredential instance
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
request – OAuth1Request instance.
(status_code, body, headers)
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)
request – OAuth1Request instance
The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
nonce – A string value of oauth_nonce
request – OAuth1Request instance
Boolean
Get client instance with the given client_id
.
client_id – A string of client_id
Client instance
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)
request – OAuth1Request instance
TemporaryCredential instance
Extend signature method verification.
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 the request for resource owner authorization.
Validate oauth_signature
from HTTP request.
request – OAuth1Request instance
Validate HTTP request for temporary credentials.
Validate oauth_timestamp
and oauth_nonce
in HTTP request.
request – OAuth1Request instance
Validate request for issuing token.
Get client instance with the given client_id
.
client_id – A string of client_id
Client instance
Fetch the token credential from data store like a database, framework should implement this function.
request – OAuth1Request instance
Token model instance
The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
nonce – A string value of oauth_nonce
request – OAuth1Request instance
Boolean
New in version v0.12.
This section is not a step by step guide on how to create an OAuth 2.0 provider in Django. Instead, we will learn how the Django implementation works, and some technical details in an OAuth 2.0 provider.
At the very beginning, we need to have some basic understanding of the OAuth 2.0.
Important
If you are developing on your localhost, remember to set the environment variable:
export AUTHLIB_INSECURE_TRANSPORT=true
Looking for Django OAuth 2.0 client? Check out Django OAuth Client.
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.
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 uses their username and password to exchange an access token,
this grant type should be used only when the client is trustworthy, implement
it with a subclass of ResourceOwnerPasswordCredentialsGrant
:
from authlib.oauth2.rfc6749 import grants
from django.contrib.auth.models import User
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
def authenticate_user(self, username, password):
try:
user = User.objects.get(username=username)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
# register it to grant endpoint
server.register_grant(PasswordGrant)
Default allowed Client Authentication Methods: client_secret_basic
.
You can add more in the subclass:
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
Client credentials grant type can access public resources and the client’s creator’s resources. It can be easily registered with:
from authlib.oauth2.rfc6749 import grants
# register it to grant endpoint
server.register_grant(grants.ClientCredentialsGrant)
Default allowed Client Authentication Methods: client_secret_basic
.
You can add more in the subclass:
class ClientCredentialsGrant(grants.ClientCredentialsGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
Many OAuth 2 providers haven’t implemented refresh token endpoint. Authlib
provides it as a grant type, implement it with a subclass of
RefreshTokenGrant
:
from authlib.oauth2.rfc6749 import grants
class RefreshTokenGrant(grants.RefreshTokenGrant):
def authenticate_refresh_token(self, refresh_token):
try:
item = OAuth2Token.objects.get(refresh_token=refresh_token)
if item.is_refresh_token_active():
return item
except OAuth2Token.DoesNotExist:
return None
def authenticate_user(self, credential):
return credential.user
def revoke_old_credential(self, credential):
credential.revoked = True
credential.save()
# register it to grant endpoint
server.register_grant(RefreshTokenGrant)
Default allowed Client Authentication Methods: client_secret_basic
.
You can add more in the subclass:
class RefreshTokenGrant(grants.RefreshTokenGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
By default, RefreshTokenGrant will not issue a refresh_token
in the token
response. Developers can change this behavior with:
class RefreshTokenGrant(grants.RefreshTokenGrant):
INCLUDE_NEW_REFRESH_TOKEN = True
It is also possible to create your own grant types. In Authlib, a Grant supports two endpoints:
Authorization Endpoint: which can handle requests with response_type
.
Token Endpoint: which is the endpoint to issue tokens.
Creating a custom grant type with BaseGrant:
from authlib.oauth2.rfc6749.grants import (
BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin
)
class MyCustomGrant(BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin):
GRANT_TYPE = 'custom-grant-type-name'
def validate_authorization_request(self):
# only needed if using AuthorizationEndpointMixin
def create_authorization_response(self, grant_user):
# only needed if using AuthorizationEndpointMixin
def validate_token_request(self):
# only needed if using TokenEndpointMixin
def create_token_response(self):
# only needed if using TokenEndpointMixin
For a better understanding, you can read the source code of the built-in grant types. And there are extended grant types defined by other specs:
Grant can accept extensions. Developers can pass extensions when registering grant:
server.register_grant(AuthorizationCodeGrant, [extension])
For instance, there is CodeChallenge
extension in Authlib:
server.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=False)])
Learn more about CodeChallenge
at RFC7636: Proof Key for Code Exchange by OAuth Public Clients.
Django OAuth 2.0 authorization server has a method to register other token
endpoints: authorization_server.register_endpoint
. Available endpoints
for now:
Revocation Endpoint from RFC7009
Introspection Endpoint from RFC7662
The revocation endpoint for OAuth authorization servers allows clients to notify the authorization server that a previously obtained refresh or access token is no longer needed.
This allows the authorization server to clean up security credentials. A revocation request will invalidate the actual token and, if applicable, other tokens based on the same authorization grant.
For example, a client may request the revocation of a refresh token with the following request:
POST /oauth/revoke HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
In Authlib Django OAuth 2.0 provider, we can simply add this feature:
from authlib.integrations.django_oauth2 import RevocationEndpoint
from django.views.decorators.http import require_http_methods
# see Authorization Server chapter
server.register_endpoint(RevocationEndpoint)
@require_http_methods(["POST"])
def revoke_token(request):
return server.create_endpoint_response(RevocationEndpoint.ENDPOINT_NAME, request)
That’s all we need. Add this revoke_token
to your routes to enable it. The suggested
url path is /oauth/revoke
.
Check Register Introspection Endpoint to get more details.
Protect users resources, so that only the authorized clients with the authorized access token can access the given scope resources.
A resource server can be a different server other than the authorization server. Here is the way to protect your users’ resources in Django:
from authlib.integrations.django_oauth2 import ResourceProtector, BearerTokenValidator
from django.http import JsonResponse
require_oauth = ResourceProtector()
require_oauth.register_token_validator(BearerTokenValidator(OAuth2Token))
@require_oauth('profile')
def user_profile(request):
user = request.oauth_token.user
return JsonResponse(dict(sub=user.pk, username=user.username))
If the resource is not protected by a scope, use None
:
@require_oauth()
def user_profile(request):
user = request.oauth_token.user
return JsonResponse(dict(sub=user.pk, username=user.username))
# or with None
@require_oauth(None)
def user_profile(request):
user = request.oauth_token.user
return JsonResponse(dict(sub=user.pk, username=user.username))
The decorator require_oauth
will add an oauth_token
property on request
,
which is the instance of current in-use Token.
Changed in version v1.0.
You can apply multiple scopes to one endpoint in AND, OR and mix modes. Here are some examples:
@require_oauth(['profile email'])
def user_profile(request):
user = request.oauth_token.user
return JsonResponse(dict(sub=user.pk, username=user.username))
It requires the token containing both profile
and email
scope.
@require_oauth(['profile', 'email'])
def user_profile(request):
user = request.oauth_token.user
return JsonResponse(dict(sub=user.pk, username=user.username))
It requires the token containing either profile
or email
scope.
It is also possible to mix AND and OR logic. e.g.:
@app.route('/profile')
@require_oauth(['profile email', 'user'])
def user_profile(request):
user = request.oauth_token.user
return JsonResponse(dict(sub=user.pk, username=user.username))
This means if the token will be valid if:
token contains both profile
and email
scope
or token contains user
scope
require_oauth
¶There is one more parameter for require_oauth
which is used to serve
public endpoints:
@require_oauth(optional=True)
def timeline_api(request):
if request.oauth_token:
return get_user_timeline(request.oauth_token.user)
return get_public_timeline(request)
OpenID Connect 1.0 are built custom grant types and grant extensions. You need to read the Authorization Server chapter at first.
Looking for OpenID Connect Client? Head over to Django OAuth Client.
OpenID Connect 1.0 uses JWT a lot. Make sure you have the basic understanding of JOSE Guide.
For OpenID Connect, we need to understand at lease four concepts:
alg: Algorithm for JWT
key: Private key for JWT
iss: Issuer value for JWT
exp: JWT expires time
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
.
A private key is required to generate JWT. The key that you are going to use
dependents on the alg
you are using. For instance, the alg is RS256
,
you need to use an RSA private key. It can be set with:
key = '''-----BEGIN RSA PRIVATE KEY-----\nMIIEog...'''
# or in JWK format
key = {"kty": "RSA", "n": ...}
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"}
OpenID Connect authorization code flow relies on the OAuth2 authorization code
flow and extends it. In OpenID Connect, there will be a nonce
parameter in
request, we need to save it into database for later use. In this case, we have
to rewrite our AuthorizationCode
db model:
class AuthorizationCode(Model, AuthorizationCodeMixin):
user = ForeignKey(User, on_delete=CASCADE)
client_id = CharField(max_length=48, db_index=True)
code = CharField(max_length=120, unique=True, null=False)
redirect_uri = TextField(default='', null=True)
response_type = TextField(default='')
scope = TextField(default='', null=True)
auth_time = IntegerField(null=False, default=now_timestamp)
# add nonce
nonce = CharField(max_length=120, default='', null=True)
# ... other fields and methods ...
OpenID Connect Code flow is the same as Authorization Code flow, but with
extended features. We can apply the OpenIDCode
extension to
AuthorizationCodeGrant
.
First, we need to implement the missing methods for OpenIDCode
:
from authlib.oidc.core import grants, UserInfo
class OpenIDCode(grants.OpenIDCode):
def exists_nonce(self, nonce, request):
try:
AuthorizationCode.objects.get(
client_id=request.client_id, nonce=nonce
)
return True
except AuthorizationCode.DoesNotExist:
return False
def get_jwt_config(self, grant):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'https://example.com',
'exp': 3600
}
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=str(user.pk), name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
Second, since there is one more nonce
value in AuthorizationCode
data,
we need to save this value into database. In this case, we have to update our
AuthorizationCodeGrant.save_authorization_code
method:
class AuthorizationCodeGrant(_AuthorizationCodeGrant):
def save_authorization_code(self, code, request):
# openid request MAY have "nonce" parameter
nonce = request.data.get('nonce')
client = request.client
auth_code = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user=request.user,
nonce=nonce,
)
auth_code.save()
return auth_code
Finally, you can register AuthorizationCodeGrant
with OpenIDCode
extension:
# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant, [OpenIDCode(require_nonce=True)])
The difference between OpenID Code flow and the standard code flow is that OpenID Connect request has a scope of “openid”:
GET /authorize?
response_type=code
&scope=openid%20profile%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1
Host: server.example.com
With the example above, you will also have to change the scope of your client
in your application to something like openid profile email
.
Now that you added the openid
scope to your application, an OpenID token
will be provided to this app whenever a client asks for a token with an
openid
scope.
The Implicit Flow is mainly used by Clients implemented in a browser using
a scripting language. You need to implement the missing methods of
OpenIDImplicitGrant
before register it:
from authlib.oidc.core import grants
class OpenIDImplicitGrant(grants.OpenIDImplicitGrant):
def exists_nonce(self, nonce, request):
try:
AuthorizationCode.objects.get(
client_id=request.client_id, nonce=nonce)
)
return True
except AuthorizationCode.DoesNotExist:
return False
def get_jwt_config(self):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'https://example.com',
'exp': 3600
}
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
server.register_grant(OpenIDImplicitGrant)
Hybrid flow is a mix of the code flow and implicit flow. You only need to implement the authorization endpoint part, token endpoint will be handled by Authorization Code Flow.
OpenIDHybridGrant is a subclass of OpenIDImplicitGrant, so the missing methods
are the same, except that OpenIDHybridGrant has one more missing method, that
is save_authorization_code
. You can implement it like this:
from authlib.oidc.core import grants
class OpenIDHybridGrant(grants.OpenIDHybridGrant):
def save_authorization_code(self, code, request):
# openid request MAY have "nonce" parameter
nonce = request.data.get('nonce')
client = request.client
auth_code = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user=request.user,
nonce=nonce,
)
auth_code.save()
return auth_code
def exists_nonce(self, nonce, request):
try:
AuthorizationCode.objects.get(
client_id=request.client_id, nonce=nonce)
)
return True
except AuthorizationCode.DoesNotExist:
return False
def get_jwt_config(self):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'https://example.com',
'exp': 3600
}
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
# register it to grant endpoint
server.register_grant(OpenIDHybridGrant)
Since all OpenID Connect Flow requires exists_nonce
, get_jwt_config
and generate_user_info
methods, you can create shared functions for them.
This part of the documentation covers the interface of Django OAuth 2.0 Server.
Django implementation of authlib.oauth2.rfc6749.AuthorizationServer
.
Initialize it with client model and token model:
from authlib.integrations.django_oauth2 import AuthorizationServer
from your_project.models import OAuth2Client, OAuth2Token
server = AuthorizationServer(OAuth2Client, OAuth2Token)
Validate authorization request and create authorization response.
request – HTTP request instance.
grant_user – if granted, it is resource owner. If denied, it is None.
Response
Validate endpoint request and create endpoint response.
name – Endpoint name
request – HTTP request instance.
Response
Validate token request and create token response.
request – HTTP request instance
Validate current HTTP request for authorization page. This page is designed for resource owner to grant or deny the authorization.
Add extra endpoint to authorization server. e.g. RevocationEndpoint:
authorization_server.register_endpoint(RevocationEndpoint)
endpoint_cls – A endpoint class
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)
grant_cls – a grant class.
extensions – extensions for the grant class.
A method to acquire current valid token with the given scope.
request – Django HTTP request instance
scopes – a list of scope values
token object
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)
token_string – A string to represent the access_token.
token
The revocation endpoint for OAuth authorization servers allows clients to notify the authorization server that a previously obtained refresh or access token is no longer needed.
Register it into authorization server, and create token endpoint response for token revocation:
from django.views.decorators.http import require_http_methods
# see register into authorization server instance
server.register_endpoint(RevocationEndpoint)
@require_http_methods(["POST"])
def revoke_token(request):
return server.create_endpoint_response(
RevocationEndpoint.ENDPOINT_NAME,
request
)
Query requested token from database.
Mark the give token as revoked.
Signal when client is authenticated
Signal when token is revoked
Signal when token is authenticated
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.
This section contains the generic implementation of RFC5849. Learn how to create an OAuth 1.0 provider in these frameworks:
Flask: Flask OAuth 1.0 Server.
Django: Django OAuth 1.0 Server.
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
request – OAuth1Request instance.
grant_user – if granted, pass the grant user, otherwise None.
(status_code, body, headers)
Create and bind oauth_verifier
to temporary credential. It
could be re-implemented in this way:
def create_authorization_verifier(self, request):
verifier = generate_token(36)
temporary_credential = request.credential
user_id = request.user.id
temporary_credential.user_id = user_id
temporary_credential.oauth_verifier = verifier
# if the credential has a save method
temporary_credential.save()
# remember to return the verifier
return verifier
request – OAuth1Request instance
A string of oauth_verifier
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
request – OAuth1Request instance
TemporaryCredential instance
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
request – OAuth1Request instance.
(status_code, body, headers)
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
request – OAuth1Request instance
TokenCredential instance
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
request – OAuth1Request instance.
(status_code, body, headers)
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)
request – OAuth1Request instance
The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
nonce – A string value of oauth_nonce
request – OAuth1Request instance
Boolean
Get client instance with the given client_id
.
client_id – A string of client_id
Client instance
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)
request – OAuth1Request instance
TemporaryCredential instance
Extend signature method verification.
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 the request for resource owner authorization.
Validate oauth_signature
from HTTP request.
request – OAuth1Request instance
Validate HTTP request for temporary credentials.
Validate oauth_timestamp
and oauth_nonce
in HTTP request.
request – OAuth1Request instance
Validate request for issuing token.
The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
nonce – A string value of oauth_nonce
request – OAuth1Request instance
Boolean
Get client instance with the given client_id
.
client_id – A string of client_id
Client instance
Fetch the token credential from data store like a database, framework should implement this function.
request – OAuth1Request instance
Token model instance
Extend signature method verification.
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
from HTTP request.
request – OAuth1Request instance
Validate oauth_timestamp
and oauth_nonce
in HTTP request.
request – OAuth1Request instance
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
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
A URL string
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
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
Boolean
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
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
A string
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
A string
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
A URL string
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
A string
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
A string
This section contains the generic implementation of RFC7009.
The revocation endpoint can be easily registered to Flask OAuth 2.0 Server or Django OAuth 2.0 Server. But there are missing methods to be implemented:
from authlib.oauth2.rfc7009 import RevocationEndpoint
class MyRevocationEndpoint(RevocationEndpoint):
def query_token(self, token, token_type_hint, client):
q = Token.query.filter_by(client_id=client.client_id)
if token_type_hint == 'access_token':
return q.filter_by(access_token=token).first()
elif token_type_hint == 'refresh_token':
return q.filter_by(refresh_token=token).first()
# without token_type_hint
item = q.filter_by(access_token=token).first()
if item:
return item
return q.filter_by(refresh_token=token).first()
def revoke_token(self, token):
token.revoked = True
db.session.add(token)
db.session.commit()
# register it to authorization server
authorization_server.register_endpoint(MyRevocationEndpoint)
After the registration, you can create a response with:
@app.route('/oauth/revoke', methods=['POST'])
def revoke_token():
return server.create_endpoint_response(MyRevocationEndpoint.ENDPOINT_NAME)
Implementation of revocation endpoint which is described in RFC7009.
Endpoint name to be registered
The client constructs the request by including the following parameters using the “application/x-www-form-urlencoded” format in the HTTP request entity-body:
REQUIRED. The token that the client wants to get revoked.
OPTIONAL. A hint about the type of the token submitted for revocation.
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
(status_code, body, headers)
Get the token from database/storage by the given token string. Developers should implement this method:
def query_token(self, token_string, token_type_hint):
if token_type_hint == 'access_token':
return Token.query_by_access_token(token_string)
if token_type_hint == 'refresh_token':
return Token.query_by_refresh_token(token_string)
return Token.query_by_access_token(token_string) or Token.query_by_refresh_token(token_string)
Mark token as revoked. Since token MUST be unique, it would be dangerous to delete it. Consider this situation:
Jane obtained a token XYZ
Jane revoked (deleted) token XYZ
Bob generated a new token XYZ
Jane can use XYZ to access Bob’s resource
It would be secure to mark a token as revoked:
def revoke_token(self, token, request):
hint = request.form.get('token_type_hint')
if hint == 'access_token':
token.access_token_revoked = True
else:
token.access_token_revoked = True
token.refresh_token_revoked = True
token.save()
Authentication client for endpoint with CLIENT_AUTH_METHODS
.
This section contains the generic implementation of RFC7515. Find how to use it in JWS Guide.
Registered Header Parameter Names defined by Section 4.1
Defined available JWS algorithms in the registry
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)
protected – A dict of protected header
payload – A bytes/string of payload
key – Private key used to generate signature
byte
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.
s – text of JWS Compact Serialization
key – key used to verify the signature
decode – a function to decode payload data
JWSObject
BadSignatureError
Generate a JWS JSON Serialization. The JWS JSON Serialization represents digitally signed or MACed content as a JSON object, per Section 7.2.
header_obj – A dict/list of header
payload – A string/dict of payload
key – Private key used to generate signature
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.
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.
obj – text of JWS JSON Serialization
key – key used to verify the signature
decode – a function to decode payload data
JWSObject
BadSignatureError
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()
.
header – A dict/list of header
payload – A string/dict of payload
key – Private key used to generate signature
byte/dict
Deserialize JWS Serialization, both compact and JSON format. It will automatically deserialize depending on the given JWS.
s – text of JWS Compact/JSON Serialization
key – key used to verify the signature
decode – a function to decode payload data
dict
BadSignatureError
If key is not provided, it will still deserialize the serialization without verification.
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
protected – dict of protected header
header – dict of unprotected header
A dict instance to represent a JWS object.
Interface for JWS algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWS with this base implementation.
Prepare key for signing and verifying signature.
Sign the text msg with a private/sign key.
msg – message bytes to be signed
key – private key to sign the message
bytes
Verify the signature of text msg with a public/verify key.
msg – message bytes to be signed
sig – result signature to be compared
key – public key to verify the signature
boolean
This section contains the generic implementation of RFC7516. Find how to use it in JWE Guide.
Registered Header Parameter Names defined by Section 4.1
Register an algorithm for alg
or enc
or zip
of JWE.
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.
protected – A dict of protected header
payload – Payload (bytes or a value convertible to bytes)
key – Public key used to encrypt payload
sender_key – Sender’s private key in case JWEAlgorithmWithTagAwareKeyAgreement is used
JWE compact serialization as bytes
Generate a JWE JSON Serialization (in fully general syntax).
The JWE JSON Serialization represents encrypted content as a JSON object. This representation is neither optimized for compactness nor URL safe.
The following members are defined for use in top-level JSON objects used for the fully general JWE JSON Serialization syntax:
The “protected” member MUST be present and contain the value BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected Header value is non-empty; otherwise, it MUST be absent. These Header Parameter values are integrity protected.
The “unprotected” member MUST be present and contain the value JWE Shared Unprotected Header when the JWE Shared Unprotected Header value is non-empty; otherwise, it MUST be absent. This value is represented as an unencoded JSON object, rather than as a string. These Header Parameter values are not integrity protected.
The “iv” member MUST be present and contain the value BASE64URL(JWE Initialization Vector) when the JWE Initialization Vector value is non-empty; otherwise, it MUST be absent.
The “aad” member MUST be present and contain the value BASE64URL(JWE AAD)) when the JWE AAD value is non-empty; otherwise, it MUST be absent. A JWE AAD value can be included to supply a base64url-encoded value to be integrity protected but not encrypted.
The “ciphertext” member MUST be present and contain the value BASE64URL(JWE Ciphertext).
The “tag” member MUST be present and contain the value BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag value is non-empty; otherwise, it MUST be absent.
The “recipients” member value MUST be an array of JSON objects. Each object contains information specific to a single recipient. This member MUST be present with exactly one array element per recipient, even if some or all of the array element values are the empty JSON object “{}” (which can happen when all Header Parameter values are shared between all recipients and when no encrypted key is used, such as when doing Direct Encryption).
The following members are defined for use in the JSON objects that are elements of the “recipients” array:
The “header” member MUST be present and contain the value JWE Per- Recipient Unprotected Header when the JWE Per-Recipient Unprotected Header value is non-empty; otherwise, it MUST be absent. This value is represented as an unencoded JSON object, rather than as a string. These Header Parameter values are not integrity protected.
The “encrypted_key” member MUST be present and contain the value BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is non-empty; otherwise, it MUST be absent.
This implementation assumes that “alg” and “enc” header fields are contained in the protected or shared unprotected header.
header_obj – A dict of headers (in addition optionally contains JWE AAD)
payload – Payload (bytes or a value convertible to bytes)
keys – Public keys (or a single public key) used to encrypt payload
sender_key – Sender’s private key in case JWEAlgorithmWithTagAwareKeyAgreement is used
JWE JSON serialization (in fully general syntax) as dict
Example of header_obj:
{
"protected": {
"alg": "ECDH-1PU+A128KW",
"enc": "A256CBC-HS512",
"apu": "QWxpY2U",
"apv": "Qm9iIGFuZCBDaGFybGll"
},
"unprotected": {
"jku": "https://alice.example.com/keys.jwks"
},
"recipients": [
{
"header": {
"kid": "bob-key-2"
}
},
{
"header": {
"kid": "2021-05-06"
}
}
],
"aad": b'Authenticate me too.'
}
Generate a JWE Serialization.
It will automatically generate a compact or JSON serialization depending on header argument. If header is a dict with “protected”, “unprotected” and/or “recipients” keys, it will call serialize_json, otherwise it will call serialize_compact.
header – A dict of header(s)
payload – Payload (bytes or a value convertible to bytes)
key – Public key(s) used to encrypt payload
sender_key – Sender’s private key in case JWEAlgorithmWithTagAwareKeyAgreement is used
JWE compact serialization as bytes or JWE JSON serialization as dict
Extract JWE Compact Serialization.
s – JWE Compact Serialization as bytes
key – Private key used to decrypt payload (optionally can be a tuple of kid and essentially key)
decode – Function to decode payload data
sender_key – Sender’s public key in case JWEAlgorithmWithTagAwareKeyAgreement is used
dict with header and payload keys where header value is a dict containing protected header fields
Extract JWE JSON Serialization.
obj – JWE JSON Serialization as dict or str
key – Private key used to decrypt payload (optionally can be a tuple of kid and essentially key)
decode – Function to decode payload data
sender_key – Sender’s public key in case JWEAlgorithmWithTagAwareKeyAgreement is used
dict with header and payload keys where header value is a dict containing protected, unprotected, recipients and/or aad keys
Extract a JWE Serialization.
It supports both compact and JSON serialization.
obj – JWE compact serialization as bytes or JWE JSON serialization as dict or str
key – Private key used to decrypt payload (optionally can be a tuple of kid and essentially key)
decode – Function to decode payload data
sender_key – Sender’s public key in case JWEAlgorithmWithTagAwareKeyAgreement is used
dict with header and payload keys
Parse JWE JSON Serialization.
obj – JWE JSON Serialization as str or dict
Parsed JWE JSON Serialization as dict if obj is an str, or obj as is if obj is already a dict
Interface for JWE algorithm conforming to RFC7518. JWA specification (RFC7518) SHOULD implement the algorithms for JWE with this base implementation.
Encrypt the given “msg” text.
msg – text to be encrypt in bytes
aad – additional authenticated data in bytes
iv – initialization vector in bytes
key – encrypted key in bytes
(ciphertext, tag)
Decrypt the given cipher text.
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
message
This section contains the generic implementation of RFC7517. Find how to use it in JWK Guide.
Generate a Key with the given key type, curve name or bit size.
kty – string of oct
, RSA
, EC
, OKP
crv_or_size – curve name or bit size
options – a dict of other options for Key
is_private – create a private key or public key
Key instance
Import a Key from bytes, string, PEM or dict.
Key instance
Import KeySet from string, dict or a list of keys.
KeySet instance
This is the base class for a JSON Web Key.
Check if the given key_op is supported by this key.
operation – key operation value, such as “sign”, “encrypt”.
ValueError
Represent this key as a JSON string.
Implementation of RFC7638 JSON Web Key (JWK) Thumbprint.
This class represents a JSON Web Key Set.
Represent this key as a dict of the JSON Web Key Set.
Represent this key set as a JSON string.
Find the key matches the given kid value.
kid – A string of kid
Key instance
ValueError
Key class of the oct
key type.
Get the raw key for the given key_op. This method will also check if the given key_op is supported by this key.
operation – key operation value, such as “sign”, “encrypt”.
raw key
Import a key from bytes, string, or dict data.
Generate a OctKey
with the given bit size.
This section contains the generic implementation of RFC7518.
The interface for JWS Algorithms are all inherit from
authlib.jose.JWSAlgorithm
.
Find how to use them in JSON Web Signature (JWS).
This section is defined by RFC7518 Section 3.2.
HS256: HMAC using SHA-256
HS384: HMAC using SHA-384
HS512: HMAC using SHA-512
Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.3.
RS256: RSASSA-PKCS1-v1_5 using SHA-256
RS384: RSASSA-PKCS1-v1_5 using SHA-384
RS512: RSASSA-PKCS1-v1_5 using SHA-384
Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.4.
ES256: ECDSA using P-256 and SHA-256
ES384: ECDSA using P-384 and SHA-384
ES512: ECDSA using P-521 and SHA-512
Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.5.
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
This section contains algorithms for JWE alg
and enc
header. For
alg
the interface are all inherited from
authlib.jose.JWEAlgorithm
. For enc
, the interface are
inherited from authlib.jose.JWEEncAlgorithm
.
Current available algorithms for alg
:
dir: Direct use of a shared symmetric key
RSA1_5: RSAES-PKCS1-v1_5
RSA-OAEP: RSAES OAEP using default parameters
RSA-OAEP-256: RSAES OAEP using SHA-256 and MGF1 with SHA-256
A128KW: AES Key Wrap with default initial value using 128-bit key
A192KW: AES Key Wrap with default initial value using 192-bit key
A256KW: AES Key Wrap with default initial value using 256-bit key
A128GCMKW: Key wrapping with AES GCM using 128-bit key
A192GCMKW: Key wrapping with AES GCM using 192-bit key
A256GCMKW: Key wrapping with AES GCM using 256-bit key
ECDH-ES: In the Direct Key Agreement mode
ECDH-ES+A128KW: using Concat KDF and CEK wrapped with A128KW
ECDH-ES+A192KW: using Concat KDF and CEK wrapped with A192KW
ECDH-ES+A256KW: using Concat KDF and CEK wrapped with A256KW
Current available algorithms for enc
:
A128CBC-HS256
A192CBC-HS384
A256CBC-HS512
A128GCM
A192GCM
A256GCM
Current available algorithms for zip
:
DEF
This section defines the parameters for keys using the algorithms via RFC7518 Section 6.
Find how to use them in JSON Web Key (JWK).
This section contains the generic Python implementation of RFC7519. Find how to use it in JWT Guide.
Check if payload contains sensitive information.
Encode a JWT with the given header, payload and key.
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
bytes
Decode the JWS with the given key. This is similar with
verify()
, except that it will raise BadSignatureError when
signature doesn’t match.
s – text of JWT
key – key used to verify the signature
claims_cls – class to be used for JWT claims
claims_options – options parameters for claims_cls
claims_params – params parameters for claims_cls
claims_cls instance
BadSignatureError
Validate everything in claims payload.
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.
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.
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.
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.
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.
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.
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.
This section contains the generic implementation of RFC7591. OAuth 2.0 Dynamic Client Registration Protocol allows developers creating OAuth client via API through Authorization Server.
To integrate with Authlib Flask OAuth 2.0 Server or Django OAuth 2.0 Server,
developers MUST implement the missing methods of ClientRegistrationEndpoint
.
The client registration endpoint accepts client metadata as JSON payload via
POST request. The metadata may contain a JWT software_statement
value. Endpoint can choose if it support software_statement
, it is not enabled
by default.
Changed in version v0.15: ClientRegistrationEndpoint has a breaking change in v0.15. Method of
authenticate_user
is replaced by authenticate_token
, and parameters in
save_client
is also changed.
Before register the endpoint, developers MUST implement the missing methods:
from authlib.oauth2.rfc7591 import ClientRegistrationEndpoint
class MyClientRegistrationEndpoint(ClientRegistrationEndpoint):
def authenticate_token(self, request):
# this method is used to find who is going to create the client
auth_header = request.headers.get('Authorization')
# bearer a-token-string
bearer_token = auth_header.split()[1]
token = Token.query.get(bearer_token)
return token
def save_client(self, client_info, client_metadata, request):
client = OAuthClient(
user_id=request.credential.user_id,
client_id=client_info['client_id'],
client_secret=client_info['client_secret'],
**client_metadata,
)
client.save()
return client
If developers want to support software_statement
, additional methods
should be implemented:
class MyClientRegistrationEndpoint(ClientRegistrationEndpoint):
# adding this to support JWT with RS256 alg, you may change it to other alg values
software_statement_alg_values_supported = ['RS256']
def resolve_public_key(self, request):
# the authenticated user's public key
# can be a string, bytes, jwk and jwk set
return request.user.public_jwk_set
Register this endpoint and use this endpoint in routes:
authorization_server.register_endpoint(MyClientRegistrationEndpoint)
# for Flask
@app.route('/register', methods=['POST'])
def client_registration():
return authorization_server.create_endpoint_response('client_registration')
# for Django
from django.views.decorators.http import require_http_methods
@require_http_methods(["POST"])
def client_registration(request):
return authorization_server.create_endpoint_response('client_registration', request)
The client registration endpoint is an OAuth 2.0 endpoint designed to allow a client to be registered with the authorization server.
Rewrite this value with a list to support software_statement
e.g. software_statement_alg_values_supported = ['RS256']
Generate claims options validation from Authorization Server metadata.
Generate `registration_client_uri
and registration_access_token
for RFC7592. This method returns None
by default. Developers MAY rewrite
this method to return registration information.
Generate client_id
value. Developers MAY rewrite this method
to use their own way to generate client_id
.
Generate client_secret
value. Developers MAY rewrite this method
to use their own way to generate client_secret
.
Return server metadata which includes supported grant types, response types and etc.
Authenticate current credential who is requesting to register a client. Developers MUST implement this method in subclass:
def authenticate_token(self, request):
auth = request.headers.get('Authorization')
return get_token_by_auth(auth)
token instance
Resolve a public key for decoding software_statement
. If
enable_software_statement=True
, developers MUST implement this
method in subclass:
def resolve_public_key(self, request):
return get_public_key_from_user(request.credential)
JWK or Key string
Save client into database. Developers MUST implement this method in subclass:
def save_client(self, client_info, client_metadata, request):
client = OAuthClient(
client_id=client_info['client_id'],
client_secret=client_info['client_secret'],
...
)
client.save()
return client
Array of redirection URI strings for use in redirect-based flows such as the authorization code and implicit flows. As required by Section 2 of OAuth 2.0 [RFC6749], clients using flows with redirection MUST register their redirection URI values. Authorization servers that support dynamic registration for redirect-based flows MUST implement support for this metadata value.
String indicator of the requested authentication method for the token endpoint.
Array of OAuth 2.0 grant type strings that the client can use at the token endpoint.
Array of the OAuth 2.0 response type strings that the client can use at the authorization endpoint.
Human-readable string name of the client to be presented to the end-user during authorization. If omitted, the authorization server MAY display the raw “client_id” value to the end-user instead. It is RECOMMENDED that clients always send this field. The value of this field MAY be internationalized, as described in Section 2.2.
URL string of a web page providing information about the client. If present, the server SHOULD display this URL to the end-user in a clickable fashion. It is RECOMMENDED that clients always send this field. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.
URL string that references a logo for the client. If present, the server SHOULD display this image to the end-user during approval. The value of this field MUST point to a valid image file. The value of this field MAY be internationalized, as described in Section 2.2.
String containing a space-separated list of scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) that the client can use when requesting access tokens. The semantics of values in this list are service specific. If omitted, an authorization server MAY register a client with a default set of scopes.
Array of strings representing ways to contact people responsible for this client, typically email addresses. The authorization server MAY make these contact addresses available to end-users for support requests for the client. See Section 6 for information on Privacy Considerations.
URL string that points to a human-readable terms of service document for the client that describes a contractual relationship between the end-user and the client that the end-user accepts when authorizing the client. The authorization server SHOULD display this URL to the end-user if it is provided. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.
URL string that points to a human-readable privacy policy document that describes how the deployment organization collects, uses, retains, and discloses personal data. The authorization server SHOULD display this URL to the end-user if it is provided. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.
URL string referencing the client’s JSON Web Key (JWK) Set [RFC7517] document, which contains the client’s public keys. The value of this field MUST point to a valid JWK Set document. These keys can be used by higher-level protocols that use signing or encryption. For instance, these keys might be used by some applications for validating signed requests made to the token endpoint when using JWTs for client authentication [RFC7523]. Use of this parameter is preferred over the “jwks” parameter, as it allows for easier key rotation. The “jwks_uri” and “jwks” parameters MUST NOT both be present in the same request or response.
Client’s JSON Web Key Set [RFC7517] document value, which contains the client’s public keys. The value of this field MUST be a JSON object containing a valid JWK Set. These keys can be used by higher-level protocols that use signing or encryption. This parameter is intended to be used by clients that cannot use the “jwks_uri” parameter, such as native clients that cannot host public URLs. The “jwks_uri” and “jwks” parameters MUST NOT both be present in the same request or response.
A unique identifier string (e.g., a Universally Unique Identifier (UUID)) assigned by the client developer or software publisher used by registration endpoints to identify the client software to be dynamically registered. Unlike “client_id”, which is issued by the authorization server and SHOULD vary between instances, the “software_id” SHOULD remain the same for all instances of the client software. The “software_id” SHOULD remain the same across multiple updates or versions of the same piece of software. The value of this field is not intended to be human readable and is usually opaque to the client and authorization server.
A version identifier string for the client software identified by “software_id”. The value of the “software_version” SHOULD change on any update to the client software identified by the same “software_id”. The value of this field is intended to be compared using string equality matching and no other comparison semantics are defined by this specification. The value of this field is outside the scope of this specification, but it is not intended to be human readable and is usually opaque to the client and authorization server. The definition of what constitutes an update to client software that would trigger a change to this value is specific to the software itself and is outside the scope of this specification.
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.
code_challenge
in Client¶Read the Code Challenge section in the Web OAuth Clients.
It is also possible to add code_challenge
in OAuth2Session
,
consider that we already have a session
:
>>> from authlib.oauth2.rfc7636 import create_s256_code_challenge
>>> code_verifier = generate_token(48)
>>> code_challenge = create_s256_code_challenge(code_verifier)
>>> uri, state = session.create_authorization_url(authorize_url, code_challenge=code_challenge, code_challenge_method='S256')
>>> # visit uri, get the response
>>> authorization_response = 'https://example.com/auth?code=42..e9&state=d..t'
>>> token = session.fetch_token(token_endpoint, authorization_response=authorization_response, code_verifier=code_verifier)
CodeChallenge extension to Authorization Code Grant. It is used to improve the security of Authorization Code flow for public clients by sending extra “code_challenge” and “code_verifier” to the authorization server.
The AuthorizationCodeGrant SHOULD save the code_challenge
and
code_challenge_method
into database when save_authorization_code
.
Then register this extension via:
server.register_grant(
AuthorizationCodeGrant,
[CodeChallenge(required=True)]
)
defaults to “plain” if not present in the request
supported code_challenge_method
Get “code_challenge” associated with this authorization code. Developers MAY re-implement it in subclass, the default logic:
def get_authorization_code_challenge(self, authorization_code):
return authorization_code.code_challenge
authorization_code – the instance of authorization_code
Get “code_challenge_method” associated with this authorization code. Developers MAY re-implement it in subclass, the default logic:
def get_authorization_code_challenge_method(self, authorization_code):
return authorization_code.code_challenge_method
authorization_code – the instance of authorization_code
New in version v0.15.
JOSE implementation in Authlib. Tracking the status of JOSE specs at https://tools.ietf.org/wg/jose/
This RFC7638 is used for computing a hash value over a JSON Web Key (JWK). The value can be used as an identity of the JWK.
The .thumbprint
method is defined on the Key
class, you can use it
directly:
from authlib.jose import JsonWebKey
raw = read_file('rsa.pem')
key = JsonWebKey.import_key(raw)
key.thumbprint()
If a key has no kid
, you can add the value of .thumbprint()
as a kid:
key['kid'] = key.thumbprint()
This method is available on every Key class, including OctKey
,
RSAKey
, ECKey
, and OKPKey
.
This section contains the generic implementation of RFC7662. OAuth 2.0 Token Introspection is usually designed to let resource servers to know content of a token.
Changed in version v1.0.
Authlib is designed to be very extendable, with the method of
.register_endpoint
on AuthorizationServer
, it is easy to add the
introspection endpoint to the authorization server. It works on both
Flask OAuth 2.0 Server and Django OAuth 2.0 Server. But first,
we need to implement the missing methods:
from authlib.oauth2.rfc7662 import IntrospectionEndpoint
class MyIntrospectionEndpoint(IntrospectionEndpoint):
def query_token(self, token, token_type_hint):
if token_type_hint == 'access_token':
tok = Token.query.filter_by(access_token=token).first()
elif token_type_hint == 'refresh_token':
tok = Token.query.filter_by(refresh_token=token).first()
else:
# without token_type_hint
tok = Token.query.filter_by(access_token=token).first()
if not tok:
tok = Token.query.filter_by(refresh_token=token).first()
return tok
def introspect_token(self, token):
return {
'active': True,
'client_id': token.client_id,
'token_type': token.token_type,
'username': get_token_username(token),
'scope': token.get_scope(),
'sub': get_token_user_sub(token),
'aud': token.client_id,
'iss': 'https://server.example.com/',
'exp': token.expires_at,
'iat': token.issued_at,
}
def check_permission(self, token, client, request):
# for example, we only allow internal client to access introspection endpoint
return client.client_type == 'internal'
# register it to authorization server
server.register_endpoint(MyIntrospectionEndpoint)
After the registration, we can create a response with:
@app.route('/oauth/introspect', methods=['POST'])
def introspect_token():
return server.create_endpoint_response(MyIntrospectionEndpoint.ENDPOINT_NAME)
New in version v1.0.
When resource server has no access to token database, it can use introspection endpoint to validate the given token. Here is how:
import requests
from authlib.oauth2.rfc7662 import IntrospectTokenValidator
from your_project import secrets
class MyIntrospectTokenValidator(IntrospectTokenValidator):
def introspect_token(self, token_string):
url = 'https://example.com/oauth/introspect'
data = {'token': token_string, 'token_type_hint': 'access_token'}
auth = (secrets.internal_client_id, secrets.internal_client_secret)
resp = requests.post(url, data=data, auth=auth)
resp.raise_for_status()
return resp.json()
We can then register this token validator in to resource protector:
require_oauth = ResourceProtector()
require_oauth.register_token_validator(MyIntrospectTokenValidator())
Please note, when using IntrospectTokenValidator
, the current_token
will be
a dict.
Implementation of introspection endpoint which is described in RFC7662.
Endpoint name to be registered
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.
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.
OPTIONAL A hint about the type of the token submitted for introspection.
Validate introspection request and create the response.
(status_code, body, headers)
Check if the request has permission to introspect the token. Developers MUST implement this method:
def check_permission(self, token, client, request):
# only allow a special client to introspect the token
return client.client_id == 'introspection_client'
bool
Get the token from database/storage by the given token string. Developers should implement this method:
def query_token(self, token_string, token_type_hint):
if token_type_hint == 'access_token':
tok = Token.query_by_access_token(token_string)
elif token_type_hint == 'refresh_token':
tok = Token.query_by_refresh_token(token_string)
else:
tok = Token.query_by_access_token(token_string)
if not tok:
tok = Token.query_by_refresh_token(token_string)
return tok
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,
}
Authentication client for endpoint with CLIENT_AUTH_METHODS
.
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)
token_string – A string to represent the access_token.
token
Request introspection token endpoint with the given token string, authorization server will return token information in JSON format. Developers MUST implement this method before using it:
def introspect_token(self, token_string):
# for example, introspection token endpoint has limited
# internal IPs to access, so there is no need to add
# authentication.
url = 'https://example.com/oauth/introspect'
resp = requests.post(url, data={'token': token_string})
resp.raise_for_status()
return resp.json()
A method to validate if the authorized token is valid, if it has the permission on the given scopes. Developers MUST re-implement this method. e.g, check if token is expired, revoked:
def validate_token(self, token, scopes, request):
if not token:
raise InvalidTokenError()
if token.is_expired() or token.is_revoked():
raise InvalidTokenError()
if not match_token_scopes(token, scopes):
raise InsufficientScopeError()
New in version v0.15.
In RFC8037, algorithm “EdDSA” is defined for JWS. Use Edwards-curve Digital Signature Algorithm (EdDSA) for signing data using “JSON Web Signature (JWS)”:
from authlib.jose import JsonWebSignature
# only allow "EdDSA" alg value
jws = JsonWebSignature(algorithms=['EdDSA'])
protected = {'alg': 'EdDSA'}
payload = b'example'
with open('ed25519-pkcs8.pem', 'rb') as f:
secret = f.read()
jws.serialize_compact(protected, payload, secret)
Learn how to use other JWS functions at JSON Web Signature (JWS).
It can also be used in JSON Web Token (JWT):
from authlib.jose import JsonWebToken
jwt = JsonWebToken(algorithms=['EdDSA'])
with open('ed25519-pkcs8.pem', 'rb') as f:
key = f.read()
header = {'alg': 'EdDSA'}
payload = {'iss': 'Authlib', 'sub': '123', ...}
s = jwt.encode(header, payload, key)
In RFC8037, algorithms “Ed25519”, “Ed448”, “X25519”, “X448” are defined for JWK. Loads and dumps Json Web Keys with:
from authlib.jose import JsonWebKey
with open('ed25519-pkcs8.pem', 'rb') as f:
key = f.read()
# MUST use "OKP" as "kty" value
JsonWebKey.import_key(key, {'kty': 'OKP'})
Learn how to use other JWK functions at JSON Web Key (JWK).
“X25519”, “X448” keys are used in “epk” for ECDH-ES algorithms.
Just use the X25519
and X448
key for ECDH-ES
in JWE:
from authlib.jose import OKPKey
from authlib.jose import JsonWebEncryption
jwe = JsonWebEncryption()
with open('X25519.pem', 'rb') as f:
key = OKPKey.import_key(f.read())
protected = {
"alg": "ECDH-ES",
"enc": "A128GCM",
"apu": "QWxpY2U",
"apv": "Qm9i",
}
jwe.serialize_compact(protected, b'hello', key)
Key class of the OKP
key type.
Define Authorization Server Metadata via Section 2 in RFC8414.
REQUIRED. The authorization server’s issuer identifier, which is a URL that uses the “https” scheme and has no query or fragment components.
URL of the authorization server’s authorization endpoint [RFC6749]. This is REQUIRED unless no grant types are supported that use the authorization endpoint.
URL of the authorization server’s token endpoint [RFC6749]. This is REQUIRED unless only the implicit grant type is supported.
OPTIONAL. URL of the authorization server’s JWK Set [JWK] document. The referenced document contains the signing key(s) the client uses to validate signatures from the authorization server. This URL MUST use the “https” scheme. The JWK Set MAY also contain the server’s encryption key or keys, which are used by clients to encrypt requests to the server. When both signing and encryption keys are made available, a “use” (public key use) parameter value is REQUIRED for all keys in the referenced JWK Set to indicate each key’s intended usage.
OPTIONAL. URL of the authorization server’s OAuth 2.0 Dynamic Client Registration endpoint [RFC7591].
RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] “scope” values that this authorization server supports. Servers MAY choose not to advertise some supported scope values even when this parameter is used.
REQUIRED. JSON array containing a list of the OAuth 2.0 “response_type” values that this authorization server supports. The array values used are the same as those used with the “response_types” parameter defined by “OAuth 2.0 Dynamic Client Registration Protocol” [RFC7591].
OPTIONAL. JSON array containing a list of the OAuth 2.0 “response_mode” values that this authorization server supports, as specified in “OAuth 2.0 Multiple Response Type Encoding Practices” [OAuth.Responses]. If omitted, the default is “[“query”, “fragment”]”. The response mode value “form_post” is also defined in “OAuth 2.0 Form Post Response Mode” [OAuth.Post].
OPTIONAL. JSON array containing a list of the OAuth 2.0 grant type values that this authorization server supports. The array values used are the same as those used with the “grant_types” parameter defined by “OAuth 2.0 Dynamic Client Registration Protocol” [RFC7591]. If omitted, the default value is “[“authorization_code”, “implicit”]”.
OPTIONAL. JSON array containing a list of client authentication methods supported by this token endpoint. Client authentication method values are used in the “token_endpoint_auth_method” parameter defined in Section 2 of [RFC7591]. If omitted, the default is “client_secret_basic” – the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749].
OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the token endpoint for the signature on the JWT [JWT] used to authenticate the client at the token endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “token_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. Servers SHOULD support “RS256”. The value “none” MUST NOT be used.
OPTIONAL. URL of a page containing human-readable information that developers might want or need to know when using the authorization server. In particular, if the authorization server does not support Dynamic Client Registration, then information on how to register clients needs to be provided in this documentation.
OPTIONAL. Languages and scripts supported for the user interface, represented as a JSON array of language tag values from BCP 47 [RFC5646]. If omitted, the set of supported languages and scripts is unspecified.
OPTIONAL. URL that the authorization server provides to the person registering the client to read about the authorization server’s requirements on how the client can use the data provided by the authorization server. The registration process SHOULD display this URL to the person registering the client if it is given. As described in Section 5, despite the identifier “op_policy_uri” appearing to be OpenID-specific, its usage in this specification is actually referring to a general OAuth 2.0 feature that is not specific to OpenID Connect.
OPTIONAL. URL that the authorization server provides to the person registering the client to read about the authorization server’s terms of service. The registration process SHOULD display this URL to the person registering the client if it is given. As described in Section 5, despite the identifier “op_tos_uri”, appearing to be OpenID-specific, its usage in this specification is actually referring to a general OAuth 2.0 feature that is not specific to OpenID Connect.
OPTIONAL. URL of the authorization server’s OAuth 2.0 revocation endpoint [RFC7009].
OPTIONAL. JSON array containing a list of client authentication methods supported by this revocation endpoint. The valid client authentication method values are those registered in the IANA “OAuth Token Endpoint Authentication Methods” registry [IANA.OAuth.Parameters]. If omitted, the default is “client_secret_basic” – the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749].
OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the revocation endpoint for the signature on the JWT [JWT] used to authenticate the client at the revocation endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “revocation_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. The value “none” MUST NOT be used.
OPTIONAL. URL of the authorization server’s OAuth 2.0 introspection endpoint [RFC7662].
OPTIONAL. JSON array containing a list of client authentication methods supported by this introspection endpoint. The valid client authentication method values are those registered in the IANA “OAuth Token Endpoint Authentication Methods” registry [IANA.OAuth.Parameters] or those registered in the IANA “OAuth Access Token Types” registry [IANA.OAuth.Parameters]. (These values are and will remain distinct, due to Section 7.2.) If omitted, the set of supported authentication methods MUST be determined by other means.
OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the introspection endpoint for the signature on the JWT [JWT] used to authenticate the client at the introspection endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “introspection_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. The value “none” MUST NOT be used.
OPTIONAL. JSON array containing a list of Proof Key for Code Exchange (PKCE) [RFC7636] code challenge methods supported by this authorization server. Code challenge method values are used in the “code_challenge_method” parameter defined in Section 4.3 of [RFC7636]. The valid code challenge method values are those registered in the IANA “PKCE Code Challenge Methods” registry [IANA.OAuth.Parameters]. If omitted, the authorization server does not support PKCE.
Validate all server metadata value.
This part of the documentation covers the specification of OpenID Connect. Learn how to use it in Flask OIDC Provider and Django OIDC Provider.
Bases: object
Provide user information for the given scope. Developers MUST implement this method in subclass, e.g.:
from authlib.oidc.core import UserInfo
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
user – user instance
scope – scope of the token
authlib.oidc.core.UserInfo
instance
Parse aud value for id_token, default value is client id. Developers MAY rewrite this method to provide a customized audience value.
Get the JWT configuration for OpenIDCode extension. The JWT
configuration will be used to generate id_token
. Developers
MUST implement this method in subclass, e.g.:
def get_jwt_config(self, grant):
return {
'key': read_private_key_file(key_path),
'alg': 'RS256',
'iss': 'issuer-identity',
'exp': 3600
}
grant – AuthorizationCodeGrant instance
dict
Bases: authlib.oidc.core.grants.code.OpenIDToken
An extension from OpenID Connect for “grant_type=code” request. Developers MUST implement the missing methods:
class MyOpenIDCode(OpenIDCode):
def get_jwt_config(self, grant):
return {...}
def exists_nonce(self, nonce, request):
return check_if_nonce_in_cache(request.client_id, nonce)
def generate_user_info(self, user, scope):
return {...}
The register this extension with AuthorizationCodeGrant:
authorization_server.register_grant(AuthorizationCodeGrant, extensions=[MyOpenIDCode()])
Check if the given nonce is existing in your database. Developers MUST implement this method in subclass, e.g.:
def exists_nonce(self, nonce, request):
exists = AuthorizationCode.query.filter_by(
client_id=request.client_id, nonce=nonce
).first()
return bool(exists)
nonce – A string of “nonce” parameter in request
request – OAuth2Request instance
Boolean
Bases: authlib.oauth2.rfc6749.grants.implicit.ImplicitGrant
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.
REQUIRED. The access token issued by the authorization server.
REQUIRED. The type of the token issued as described in Section 7.1. Value is case insensitive.
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.
OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The scope of the access token as described by Section 3.3.
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.
redirect_uri – Redirect to the given URI for the authorization
grant_user – if resource owner granted the request, pass this resource owner, otherwise pass None.
(status_code, body, headers)
Check if the given nonce is existing in your database. Developers should implement this method in subclass, e.g.:
def exists_nonce(self, nonce, request):
exists = AuthorizationCode.query.filter_by(
client_id=request.client_id, nonce=nonce
).first()
return bool(exists)
nonce – A string of “nonce” parameter in request
request – OAuth2Request instance
Boolean
Provide user information for the given scope. Developers MUST implement this method in subclass, e.g.:
from authlib.oidc.core import UserInfo
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
user – user instance
scope – scope of the token
authlib.oidc.core.UserInfo
instance
Parse aud value for id_token, default value is client id. Developers MAY rewrite this method to provide a customized audience value.
Get the JWT configuration for OpenIDImplicitGrant. The JWT
configuration will be used to generate id_token
. Developers
MUST implement this method in subclass, e.g.:
def get_jwt_config(self):
return {
'key': read_private_key_file(key_path),
'alg': 'RS256',
'iss': 'issuer-identity',
'exp': 3600
}
dict
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.
REQUIRED. Value MUST be set to “token”.
REQUIRED. The client identifier as described in Section 2.2.
OPTIONAL. As described in Section 3.1.2.
OPTIONAL. The scope of the access request as described by Section 3.3.
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
Bases: authlib.oidc.core.grants.implicit.OpenIDImplicitGrant
Generated “code” length
“The method to generate “code” value for authorization code data. Developers may rewrite this method, or customize the code length with:
class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
AUTHORIZATION_CODE_LENGTH = 32 # default is 48
Save authorization_code for later use. Developers MUST implement it in subclass. Here is an example:
def save_authorization_code(self, code, request):
client = request.client
auth_code = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
nonce=request.data.get('nonce'),
user_id=request.user.id,
)
auth_code.save()
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.
REQUIRED. Value MUST be set to “token”.
REQUIRED. The client identifier as described in Section 2.2.
OPTIONAL. As described in Section 3.1.2.
OPTIONAL. The scope of the access request as described by Section 3.3.
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
Bases: authlib.jose.rfc7519.claims.JWTClaims
Validate everything in claims payload.
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.
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.
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.
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.
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.
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.
Bases: authlib.oidc.core.claims.IDToken
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.
Bases: authlib.oidc.core.claims.ImplicitIDToken
Validate everything in claims payload.
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.
The standard claims of a UserInfo object. Defined per Section 5.1.
registered claims that UserInfo supports
This section aims to make Authlib sustainable, on governance, code commits, issues and finance.
If you use Authlib and its related projects commercially we strongly encourage you to invest in its sustainable development by sponsorship.
We accept funding with paid license and sponsorship. With the funding, it will:
contribute to faster releases, more features, and higher quality software.
allow more time to be invested in the documentation, issues, and community support.
And you can also get benefits from us:
access to some of our private repositories
access to our private PyPI.
join our security mail list.
Get more details on our sponsor tiers page at:
GitHub sponsors: https://github.com/sponsors/lepture
Patreon: https://www.patreon.com/lepture
Insiders are people who have access to our private repositories, you can become an insider with:
purchasing a paid license at https://authlib.org/plans
Become a sponsor with tiers including “Access to our private repos” benefit
We offer a private PyPI server to release early security fixes and features. You can find more details about this PyPI server at:
The following list of funding goals shows features and additional addons we are going to add.
done setup a private PyPI
todo A running demo of loginpass services
todo Starlette integration of loginpass
todo A simple running demo of OIDC provider in Flask
When the demo is complete, source code of the demo will only be available to our insiders.
In Authlib v2.0, we will start working on async provider integrations.
todo Starlette (FastAPI) OAuth 1.0 provider integration
todo Starlette (FastAPI) OAuth 2.0 provider integration
todo Starlette (FastAPI) OIDC provider integration
In Authlib v3.0, we will add built-in support for SAML.
todo SAML 2.0 implementation
todo RFC7522 (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants
todo CBOR Object Signing and Encryption
todo A complex running demo of OIDC provider
Here is our current sponsors, we keep a full list of our sponsors in the Authors page.
If you have questions or issues about Authlib, there are several options:
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.
If your StackOverflow question is not answered for a long time, please ping me at Twitter @authlib.
GitHub issues is used for but reporting and feature requests. Please don’t ask questions on GitHub issues.
If you have feature requests, please comment on Features Checklist. If they are accepted, they will be listed in the post.
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
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.
Here is the process when we have received a security report:
we will reply to you in 24 hours
we will confirm it in 2 days, if we can’t reproduce it, we will send emails to you for more information
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.
we will push the source code to GitHub when it has been released in PyPI for 1 week.
if necessary, we will retrieve a CVE after releasing to PyPI.
Note
No CVEs yet
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:
All contributions are welcome, as long as everyone involved is treated with respect.
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.
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.
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 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.
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.
Finance support is also welcome. A better finance can make Authlib Sustainable. Here I offer two options:
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.
One Time Donation
I accept one time donation via Stripe, Alipay and Wechat. Donate via
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.
An official example on how to use OAuth clients with Authlib in Flask, Django, Starlette, and FastAPI.
Source Code: https://github.com/authlib/demo-oauth-client
An official example on how to create an OAuth 2.0 server with Authlib.
Source Code: https://github.com/authlib/example-oauth2-server
An official example on how to create an OpenID Connect server with Authlib.
Source Code: https://github.com/authlib/example-oidc-server
Open source projects that are using Authlib to create an OAuth server.
Customizable and skinnable social platform dedicated to (open)data.
Documentation: https://udata.readthedocs.io/
Source Code: https://github.com/opendatateam/udata
A service that analyzes docker images and applies user-defined acceptance policies to allow automated container image validation and certification.
Source Code: https://github.com/anchore/anchore-engine
A sustainable project is trustworthy to use in your production environment. To make this project sustainable, we need your help. Here are several options:
To make Authlib sustainable, we need your contribution. There are many ways for you, some of them even don’t require code writing:
File a bug report when you found one.
Solve issues already there.
Write a blog post on Authlib.
Give a star on GitHub and spread Authlib to other people.
Authlib is accepting sponsorships via GitHub Sponsors or Patreon. You are welcome to become a backer or a sponsor.
Find out the benefits for sponsorship.
Authlib is licensed under BSD for open source projects. If you are running a business, consider to purchase a commercial license instead.
Find more information on https://authlib.org/support#commercial-license
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
Authlib offers two licenses, one is BSD for open source projects, one is a commercial license for closed source projects.
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.
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:
Stay tuned with Authlib, here is a history of Authlib changes.
Here you can see the full list of changes between each Authlib release.
Released on April 6, 2022
Fix authenticate_none method, via issue#438.
Allow to pass in alternative signing algorithm to RFC7523 authentication methods via PR#447.
Fix missing_token
for Flask OAuth client, via issue#448.
Allow openid
in any place of the scope, via issue#449.
Security fix for validating essential value on blank value in JWT, via issue#445.
Released on Mar 15, 2022.
We have dropped support for Python 2 in this release. We have removed built-in SQLAlchemy integration.
OAuth Client Changes:
The whole framework client integrations have been restructured, if you are
using the client properly, e.g. oauth.register(...)
, it would work as
before.
OAuth Provider Changes:
In Flask OAuth 2.0 provider, we have removed the deprecated
OAUTH2_JWT_XXX
configuration, instead, developers should define
.get_jwt_config on OpenID extensions and grant types.
SQLAlchemy integrations has been removed from Authlib. Developers should define the database by themselves.
JOSE Changes
JWS
has been renamed to JsonWebSignature
JWE
has been renamed to JsonWebEncryption
JWK
has been renamed to JsonWebKey
JWT
has been renamed to JsonWebToken
The “Key” model has been re-designed, checkout the JSON Web Key (JWK) for updates.
Added ES256K
algorithm for JWS and JWT.
Breaking Changes: find how to solve the deprecate issues via https://git.io/JkY4f
Released on Oct 18, 2021.
Make Authlib compatible with latest httpx
Make Authlib compatible with latest werkzeug
Allow customize RFC7523 alg
value
Released on Jul 17, 2021.
Security fix when JWT claims is None.
Released on Jan 15, 2021.
Fixed .authorize_access_token for OAuth 1.0 services, via issue#308.
Released on Oct 18, 2020.
Fixed HTTPX authentication bug, via issue#283.
Released on Oct 14, 2020.
Backward compatible fix for using JWKs in JWT, via issue#280.
Released on Oct 10, 2020.
This is the last release before v1.0. In this release, we added more RFCs implementations and did some refactors for JOSE:
RFC8037: CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE)
RFC7638: JSON Web Key (JWK) Thumbprint
We also fixed bugs for integrations:
Fixed support for HTTPX>=0.14.3
Added OAuth clients of HTTPX back via PR#270
Fixed parallel token refreshes for HTTPX async OAuth 2 client
Raise OAuthError when callback contains errors via issue#275
Breaking Change:
1. The parameter algorithms
in JsonWebSignature
and JsonWebEncryption
are changed. Usually you don’t have to care about it since you won’t use it directly.
2. Whole JSON Web Key is refactored, please check JSON Web Key (JWK).
Released on May 18, 2020.
Released on May 6, 2020.
Released on Feb 12, 2020.
Quick fix for legacy imports of Flask and Django clients
Released on Feb 11, 2020.
In this release, Authlib has introduced a new way to write framework integrations for clients.
Bug fixes and enhancements in this release:
Fix HTTPX integrations due to HTTPX breaking changes
Fix ES algorithms for JWS
Allow user given nonce
via issue#180.
Fix OAuth errors get_headers
leak.
Fix code_verifier
via issue#165.
Breaking Change: drop sync OAuth clients of HTTPX.
Find old changelog at https://github.com/lepture/authlib/releases
Version 0.13.0: Released on Nov 11, 2019
Version 0.12.0: Released on Sep 3, 2019
Version 0.11.0: Released on Apr 6, 2019
Version 0.10.0: Released on Oct 12, 2018
Version 0.9.0: Released on Aug 12, 2018
Version 0.8.0: Released on Jun 17, 2018
Version 0.7.0: Released on Apr 28, 2018
Version 0.6.0: Released on Mar 20, 2018
Version 0.5.1: Released on Feb 11, 2018
Version 0.5.0: Released on Feb 11, 2018
Version 0.4.1: Released on Feb 2, 2018
Version 0.4.0: Released on Jan 31, 2018
Version 0.3.0: Released on Dec 24, 2017
Version 0.2.1: Released on Dec 6, 2017
Version 0.2.0: Released on Nov 25, 2017
Version 0.1.0: Released on Nov 18, 2017
Consider to follow Authlib on Twitter, and subscribe Authlib Blog.