Release v0.15.2. (Installation)
The ultimate Python library in building OAuth and OpenID Connect servers. It is designed from low level specifications implementations to high level frameworks integrations, to meet the needs of everyone.
Authlib is compatible with Python2.7+ and Python3.6+. (We will drop Python 2 support when Authlib 1.0 is released)
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.
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 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:
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:
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:
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)
The second step is to generate the authorization URL:
>>> authenticate_url = 'https://api.twitter.com/oauth/authenticate'
>>> client.create_authorization_url(authenticate_url, request_token['oauth_token'])
'https://api.twitter.com/oauth/authenticate?oauth_token=gA..H'
Actually, the second parameter request_token
can be omitted, since session
is re-used:
>>> client.create_authorization_url(authenticate_url)
Now visit the authorization url that create_authorization_url generated, and grant the authorization.
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.
There are two steps in OAuth 2 to obtain an access token with authorization code grant type. Initialize the session for reuse:
>>> client_id = 'Your GitHub client ID'
>>> client_secret = 'Your GitHub client secret'
>>> scope = 'user:email' # we want to fetch user's email
>>>
>>> # using requests implementation
>>> from authlib.integrations.requests_client import OAuth2Session
>>> client = OAuth2Session(client_id, client_secret, scope=scope)
>>>
>>> # using httpx implementation
>>> from authlib.integrations.httpx_client import AsyncOAuth2Client
>>> client = AsyncOAuth2Client(client_id, client_secret, scope=scope)
You can assign a redirect_uri
in case you want to specify the callback
url.
Unlike OAuth 1, there is no request token. The first step is to jump to the remote authorization server:
>>> authorization_endpoint = 'https://github.com/login/oauth/authorize'
>>> uri, state = client.create_authorization_url(authorization_endpoint)
>>> print(uri)
https://github.com/login/oauth/authorize?response_type=code&client_id=c..id&scope=user%3Aemail&state=d..t
The create_authorization_url
returns a tuple of (uri, state)
,
in real project, you should save the state for later use.
Now head over to the generated authorization url, and grant the authorization.
The authorization server will redirect you back to your site with a code and state arguments:
https://example.com/github?code=42..e9&state=d..t
Use .fetch_token
to obtain access token. This method will also verify
the state in case of CSRF attack:
>>> authorization_response = 'https://example.com/github?code=42..e9&state=d..t'
>>> token_endpoint = 'https://github.com/login/oauth/access_token'
>>> token = client.fetch_token(token_endpoint, authorization_response=authorization_response)
>>> print(token)
{
'access_token': 'e..ad',
'token_type': 'bearer',
'scope': 'user:email'
}
Save this token to access users’ protected resources.
In real project, this session can not be re-used since you are redirected to another website. You need to create another session yourself:
>>> state = restore_previous_state()
>>>
>>> # using requests
>>> from authlib.integrations.requests_client import OAuth2Session
>>> client = OAuth2Session(client_id, client_secret, state=state)
>>>
>>> # using httpx
>>> from authlib.integrations.httpx_client import AsyncOAuth2Client
>>> client = OAuth2Client(client_id, client_secret, state=state)
>>>
>>> client.fetch_token(token_endpoint, authorization_response=authorization_response)
Authlib has a built-in Flask/Django integration. Learn from them.
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 authoirzation server will redirect back to your redirect_uri, the response url would be something like:
https://example.com/cb#access_token=2..WpA&state=xyz&token_type=bearer&expires_in=3600
Fetch access token from the fragment with .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:
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.
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.
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.
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')
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:
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:
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:
OAuth1Client
OAuth2Client
AssertionClient
AsyncOAuth1Client
AsyncOAuth2Client
AsyncAssertionClient
Note
HTTPX is still in its “alpha” stage, use it with caution.
There are three steps in OAuth 1 to obtain an access token:
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:
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:
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:
flask_client.OAuth
for Flask OAuth Clientdjango_client.OAuth
for Django OAuth Clientstarlette_client.OAuth
for Starlette OAuth ClientThe 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.
After configuring the OAuth
registry and the remote application, the
rest steps are much simpler. The only required parts are routes:
Here is the example for Twitter login:
def login(request):
twitter = oauth.create_client('twitter')
redirect_uri = 'https://example.com/authorize'
return twitter.authorize_redirect(request, redirect_uri)
def authorize(request):
twitter = oauth.create_client('twitter')
token = twitter.authorize_access_token(request)
resp = twitter.get('account/verify_credentials.json')
profile = resp.json()
# do something with the token and profile
return '...'
After user confirmed on Twitter authorization page, it will redirect
back to your website authorize
page. In this route, you can get your
user’s twitter profile information, you can store the user information
in your database, mark your user as logged in and etc.
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:
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)
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())
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())
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)
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):
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 log in with OAuth 1.0 and OAuth 2.0, “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 access token, we can call:
def authorize(request):
token = oauth.google.authorize_access_token(request)
user = oauth.google.userinfo(request)
return '...'
If the userinfo_endpoint
is not compatible with
UserInfo
, we can use a userinfo_compliance_fix
:
def compliance_fix(client, user_data):
return {
'sub': user_data['id'],
'name': user_data['name']
}
oauth.register(
'example',
client_id='...',
client_secret='...',
userinfo_endpoint='https://example.com/userinfo',
userinfo_compliance_fix=compliance_fix,
)
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 you 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.
Note
Please read Web OAuth Clients at first. Authlib has a shared API design among framework integrations, learn them from Web OAuth Clients.
Changed in version v0.13: Authlib moved all integrations into authlib.integrations
module since v0.13.
For earlier version, developers can import the Flask client with:
from authlib.flask.client import OAuth
Authlib Flask OAuth registry can load the configuration from Flask app.config
automatically. Every key value pair in .register
can be omit. They can be
configured in your Flask App configuration. Config key is formatted with
{name}_{key}
in uppercase, e.g.
TWITTER_CLIENT_ID | Twitter Consumer Key |
TWITTER_CLIENT_SECRET | Twitter Consumer Secret |
TWITTER_REQUEST_TOKEN_URL | URL to fetch OAuth request token |
If you register your remote app as oauth.register('example', ...)
, the
config key would look like:
EXAMPLE_CLIENT_ID | 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 OAuth2SessionWe 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)
Unlike the examples in Web OAuth Clients, Flask does not pass a request
into routes. In this case, the routes for authorization should look like:
from flask import url_for, render_template
@app.route('/login')
def login():
redirect_uri = url_for('authorize', _external=True)
return oauth.twitter.authorize_redirect(redirect_uri)
@app.route('/authorize')
def authorize():
token = oauth.twitter.authorize_access_token()
resp = oauth.twitter.get('account/verify_credentials.json')
profile = resp.json()
# do something with the token and profile
return redirect('/')
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')
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()
We can get the user information from the id_token
in the returned token:
userinfo = oauth.google.parse_id_token(token)
Here are some example code for you learn Flask OAuth client integrations:
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.
Note
Please read Web OAuth Clients at first. Authlib has a shared API design among framework integrations, learn them from Web OAuth Clients.
Changed in version v0.13: Authlib moved all integrations into authlib.integrations
module since v0.13.
For earlier version, developers can import the Django client with:
from authlib.django.client import OAuth
Authlib Django OAuth registry can load the configuration from your Django application settings automatically. Every key value pair can be omit. They can be configured from your Django settings:
AUTHLIB_OAUTH_CLIENTS = {
'twitter': {
'client_id': 'Twitter Consumer Key',
'client_secret': 'Twitter Consumer Secret',
'request_token_url': 'https://api.twitter.com/oauth/request_token',
'request_token_params': None,
'access_token_url': 'https://api.twitter.com/oauth/access_token',
'access_token_params': None,
'refresh_token_url': None,
'authorize_url': 'https://api.twitter.com/oauth/authenticate',
'api_base_url': 'https://api.twitter.com/1.1/',
'client_kwargs': None
}
}
We suggest that you keep ONLY client_id
and client_secret
in
your application settings, other parameters are better in .register()
.
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.
Just like the example in Web OAuth Clients, everything is the same.
But there is a hint to create redirect_uri
with request
in Django:
def login(request):
# build a full authorize callback uri
redirect_uri = request.build_absolute_uri('/authorize')
return oauth.twitter.authorize_redirect(request, redirect_uri)
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:
from django.dispatch import receiver
from authlib.integrations.django_client import token_update
@receiver(token_update)
def on_token_update(sender, 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 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)
We can get the user information from the id_token
in the returned token:
userinfo = oauth.google.parse_id_token(request, token)
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.
Just like the examples in Web OAuth Clients, but Starlette is async, the routes for authorization should look like:
@app.route('/login')
async def login(request):
google = oauth.create_client('google')
redirect_uri = request.url_for('authorize')
return await google.authorize_redirect(request, redirect_uri)
@app.route('/auth')
async def authorize(request):
google = oauth.create_client('google')
token = await google.authorize_access_token(request)
user = await google.parse_id_token(request, token)
# do something with the token and profile
return '...'
An OpenID Connect client is no different than a normal OAuth 2.0 client, just add
openid
scope when .register
. In the above example, in authorize
:
user = await google.parse_id_token(request, token)
There is a id_token
in the response token
. We can parse userinfo from this
id_token
.
Here is how you can add openid
scope in .register
:
oauth.register(
'google',
...
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid profile email'}
)
We have Starlette demos at https://github.com/authlib/demo-oauth-client
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. It is build on top of Starlette, that means most of the code looks similar with Starlette code. You should first read documentation of:
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")
def login_via_google(request: Request):
redirect_uri = 'https://example.com/auth'
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.get("/auth")
def auth_via_google(request: Request):
token = await oauth.google.authorize_access_token(request)
user = await oauth.google.parse_id_token(request, token)
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.
authlib.integrations.requests_client.
OAuth1Session
(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', force_include_body=False, **kwargs)¶Create an authorization URL by appending request_token and optional kwargs to url.
This is the second step in the OAuth 1 workflow. The user should be redirected to this authorization URL, grant access to you, and then be redirected back to you. The redirection back can either be specified during client registration or by supplying a callback URI per request.
Parameters: |
|
---|---|
Returns: | The authorization URL with new parameters embedded. |
fetch_access_token
(url, verifier=None, **kwargs)¶Method for fetching an access token from the token endpoint.
This is the final step in the OAuth 1 workflow. An access token is obtained using all previously obtained credentials, including the verifier from the authorization step.
Parameters: |
|
---|---|
Returns: | A token dict. |
fetch_request_token
(url, realm=None, **kwargs)¶Method for fetching an access token from the token endpoint.
This is the first step in the OAuth 1 workflow. A request token is obtained by making a signed post request to url. The token is then parsed from the application/x-www-form-urlencoded response and ready to be used to construct an authorization url.
Parameters: |
|
---|---|
Returns: | 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.
Parameters: | url – The full URL that resulted from the user being redirected back from the OAuth provider to you, the client. |
---|---|
Returns: | A dict of parameters extracted from the URL. |
authlib.integrations.requests_client.
OAuth1Auth
(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', realm=None, force_include_body=False)¶Signs the request using OAuth 1 (RFC5849)
authlib.integrations.requests_client.
OAuth2Session
(client_id=None, client_secret=None, token_endpoint_auth_method=None, revocation_endpoint_auth_method=None, scope=None, redirect_uri=None, token=None, token_placement='header', update_token=None, **kwargs)¶Construct a new OAuth 2 client requests session.
Parameters: |
|
---|
Generate an authorization URL and state.
Parameters: |
|
---|---|
Returns: | authorization_url, state |
fetch_token
(url=None, body='', method='POST', headers=None, auth=None, grant_type=None, **kwargs)¶Generic method for fetching an access token from the token endpoint.
Parameters: |
|
---|---|
Returns: | A |
refresh_token
(url, refresh_token=None, body='', auth=None, headers=None, **kwargs)¶Fetch a new access token using a refresh token.
Parameters: |
|
---|---|
Returns: | A |
register_client_auth_method
(auth)¶Extend client authenticate for token endpoint.
Parameters: | auth – an instance to sign the request |
---|
register_compliance_hook
(hook_type, hook)¶Register a hook for request/response tweaking.
Available hooks are:
revoke_token
(url, token=None, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)¶Revoke token method defined via RFC7009.
Parameters: |
|
---|---|
Returns: | Revocation Response |
authlib.integrations.requests_client.
OAuth2Auth
(token, token_placement='header', client=None)¶Sign requests for OAuth 2.0, currently only bearer token is supported.
authlib.integrations.httpx_client.
OAuth1Auth
(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', realm=None, force_include_body=False)¶Signs the httpx request using OAuth 1 (RFC5849)
auth_flow
(request: httpx.Request) → Generator[httpx.Request, httpx.Response, None]¶Execute the authentication flow.
To dispatch a request, yield it:
`
yield request
`
The client will .send() the response back into the flow generator. You can access it like so:
`
response = yield request
`
A return (or reaching the end of the generator) will result in the client returning the last response obtained from the server.
You can dispatch as many requests as is necessary.
authlib.integrations.httpx_client.
AsyncOAuth1Client
(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', force_include_body=False, **kwargs)¶Create an authorization URL by appending request_token and optional kwargs to url.
This is the second step in the OAuth 1 workflow. The user should be redirected to this authorization URL, grant access to you, and then be redirected back to you. The redirection back can either be specified during client registration or by supplying a callback URI per request.
Parameters: |
|
---|---|
Returns: | The authorization URL with new parameters embedded. |
fetch_access_token
(url, verifier=None, **kwargs)¶Method for fetching an access token from the token endpoint.
This is the final step in the OAuth 1 workflow. An access token is obtained using all previously obtained credentials, including the verifier from the authorization step.
Parameters: |
|
---|---|
Returns: | A token dict. |
fetch_request_token
(url, realm=None, **kwargs)¶Method for fetching an access token from the token endpoint.
This is the first step in the OAuth 1 workflow. A request token is obtained by making a signed post request to url. The token is then parsed from the application/x-www-form-urlencoded response and ready to be used to construct an authorization url.
Parameters: |
|
---|---|
Returns: | 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.
Parameters: | url – The full URL that resulted from the user being redirected back from the OAuth provider to you, the client. |
---|---|
Returns: | A dict of parameters extracted from the URL. |
authlib.integrations.httpx_client.
OAuth2Auth
(token, token_placement='header', client=None)¶Sign requests for OAuth 2.0, currently only bearer token is supported.
authlib.integrations.httpx_client.
AsyncOAuth2Client
(client_id=None, client_secret=None, token_endpoint_auth_method=None, revocation_endpoint_auth_method=None, scope=None, redirect_uri=None, token=None, token_placement='header', update_token=None, **kwargs)¶Generate an authorization URL and state.
Parameters: |
|
---|---|
Returns: | authorization_url, state |
fetch_token
(url=None, body='', method='POST', headers=None, auth=None, grant_type=None, **kwargs)¶Generic method for fetching an access token from the token endpoint.
Parameters: |
|
---|---|
Returns: | A |
refresh_token
(url, refresh_token=None, body='', auth=None, headers=None, **kwargs)¶Fetch a new access token using a refresh token.
Parameters: |
|
---|---|
Returns: | A |
register_client_auth_method
(auth)¶Extend client authenticate for token endpoint.
Parameters: | auth – an instance to sign the request |
---|
register_compliance_hook
(hook_type, hook)¶Register a hook for request/response tweaking.
Available hooks are:
revoke_token
(url, token=None, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)¶Revoke token method defined via RFC7009.
Parameters: |
|
---|---|
Returns: | Revocation Response |
authlib.integrations.httpx_client.
AsyncAssertionClient
(token_endpoint, issuer, subject, audience=None, grant_type=None, claims=None, token_placement='header', scope=None, **kwargs)¶authlib.integrations.flask_client.
OAuth
(app=None, cache=None, fetch_token=None, update_token=None)¶A Flask OAuth registry for oauth clients.
Create an instance with Flask:
oauth = OAuth(app, cache=cache)
You can also pass the instance of Flask later:
oauth = OAuth()
oauth.init_app(app, cache=cache)
Parameters: |
|
---|
create_client
(name)¶Create or get the given named OAuth client. For instance, the
OAuth registry has .register
a twitter client, developers may
access the client with:
client = oauth.create_client('twitter')
Param: | name: Name of the remote application |
---|---|
Returns: | OAuth remote app |
init_app
(app, cache=None, fetch_token=None, update_token=None)¶Initialize lazy for Flask app. This is usually used for Flask application factory pattern.
register
(name, overwrite=False, **kwargs)¶Registers a new remote application.
Parameters: |
|
---|
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')
authlib.integrations.flask_client.
FlaskRemoteApp
(framework, name=None, fetch_token=None, **kwargs)¶Flask integrated RemoteApp of OAuthClient
.
It has built-in hooks for OAuthClient. The only required configuration
is token model.
Authorize access token.
Create a HTTP Redirect for Authorization Endpoint.
Parameters: |
|
---|---|
Returns: | A HTTP redirect response. |
delete
(url, **kwargs)¶Invoke DELETE http request.
If api_base_url
configured, shortcut is available:
client.delete('posts/123')
get
(url, **kwargs)¶Invoke GET http request.
If api_base_url
configured, shortcut is available:
client.get('users/lepture')
patch
(url, **kwargs)¶Invoke PATCH http request.
If api_base_url
configured, shortcut is available:
client.patch('profile', json={'name': 'Hsiaoming Yang'})
post
(url, **kwargs)¶Invoke POST http request.
If api_base_url
configured, shortcut is available:
client.post('timeline', json={'text': 'Hi'})
put
(url, **kwargs)¶Invoke PUT http request.
If api_base_url
configured, shortcut is available:
client.put('profile', json={'name': 'Hsiaoming Yang'})
Save temporary data into session for the authorization step. These data can be retrieved later when fetching access token.
authlib.integrations.django_client.
OAuth
(fetch_token=None, update_token=None)¶create_client
(name)¶Create or get the given named OAuth client. For instance, the
OAuth registry has .register
a twitter client, developers may
access the client with:
client = oauth.create_client('twitter')
Param: | name: Name of the remote application |
---|---|
Returns: | OAuth remote app |
register
(name, overwrite=False, **kwargs)¶Registers a new remote application.
Parameters: |
|
---|
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')
authlib.integrations.django_client.
DjangoRemoteApp
(framework, name=None, fetch_token=None, update_token=None, client_id=None, client_secret=None, request_token_url=None, request_token_params=None, access_token_url=None, access_token_params=None, authorize_url=None, authorize_params=None, api_base_url=None, client_kwargs=None, server_metadata_url=None, compliance_fix=None, client_auth_methods=None, user_agent=None, **kwargs)¶Fetch access token in one step.
Parameters: | request – HTTP request instance from Django view. |
---|---|
Returns: | A token dict. |
Create a HTTP Redirect for Authorization Endpoint.
Parameters: |
|
---|---|
Returns: | A HTTP redirect response. |
delete
(url, **kwargs)¶Invoke DELETE http request.
If api_base_url
configured, shortcut is available:
client.delete('posts/123')
get
(url, **kwargs)¶Invoke GET http request.
If api_base_url
configured, shortcut is available:
client.get('users/lepture')
patch
(url, **kwargs)¶Invoke PATCH http request.
If api_base_url
configured, shortcut is available:
client.patch('profile', json={'name': 'Hsiaoming Yang'})
post
(url, **kwargs)¶Invoke POST http request.
If api_base_url
configured, shortcut is available:
client.post('timeline', json={'text': 'Hi'})
put
(url, **kwargs)¶Invoke PUT http request.
If api_base_url
configured, shortcut is available:
client.put('profile', json={'name': 'Hsiaoming Yang'})
Save temporary data into session for the authorization step. These data can be retrieved later when fetching access token.
authlib.integrations.starlette_client.
OAuth
(config=None, cache=None, fetch_token=None, update_token=None)¶create_client
(name)¶Create or get the given named OAuth client. For instance, the
OAuth registry has .register
a twitter client, developers may
access the client with:
client = oauth.create_client('twitter')
Param: | name: Name of the remote application |
---|---|
Returns: | OAuth remote app |
register
(name, overwrite=False, **kwargs)¶Registers a new remote application.
Parameters: |
|
---|
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')
authlib.integrations.starlette_client.
StarletteRemoteApp
(framework, name=None, fetch_token=None, update_token=None, client_id=None, client_secret=None, request_token_url=None, request_token_params=None, access_token_url=None, access_token_params=None, authorize_url=None, authorize_params=None, api_base_url=None, client_kwargs=None, server_metadata_url=None, compliance_fix=None, client_auth_methods=None, user_agent=None, **kwargs)¶Fetch an access token.
Parameters: | request – Starlette Request instance. |
---|---|
Returns: | A token dict. |
Create a HTTP Redirect for Authorization Endpoint.
Parameters: |
|
---|---|
Returns: | Starlette |
delete
(url, **kwargs)¶Invoke DELETE http request.
If api_base_url
configured, shortcut is available:
client.delete('posts/123')
get
(url, **kwargs)¶Invoke GET http request.
If api_base_url
configured, shortcut is available:
client.get('users/lepture')
patch
(url, **kwargs)¶Invoke PATCH http request.
If api_base_url
configured, shortcut is available:
client.patch('profile', json={'name': 'Hsiaoming Yang'})
post
(url, **kwargs)¶Invoke POST http request.
If api_base_url
configured, shortcut is available:
client.post('timeline', json={'text': 'Hi'})
put
(url, **kwargs)¶Invoke PUT http request.
If api_base_url
configured, shortcut is available:
client.put('profile', json={'name': 'Hsiaoming Yang'})
Save temporary data into session for the authorization step. These data can be retrieved later when fetching access token.
This part of the documentation contains information on the JOSE implementation. It includes:
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:
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:
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:
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']
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:
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:
The available enc
list:
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'))
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'])
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.
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:
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:
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:
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:
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:
AuthorizationCodeGrant
ImplicitGrant
ResourceOwnerPasswordCredentialsGrant
ClientCredentialsGrant
RefreshTokenGrant
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 flowOpenIDImplicitGrant
grant type for implicit flowOpenIDHybridGrant
grant type for hybrid flowThis 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:
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.
The Authorization Server provides several endpoints for temporary credentials, authorization, and issuing token credentials. When the resource owner (user) grants the authorization, this server will issue a token credential to the client.
Resource Owner is the user who is using your service. A resource owner can log in your website with username/email and password, or other methods.
A resource owner MUST implement get_user_id()
method:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
def get_user_id(self):
return self.id
A client is an application making protected resource requests on behalf of the resource owner and with its authorization. It contains at least three information:
Authlib has provided a mixin for SQLAlchemy, define the client with this mixin:
from authlib.integrations.sqla_oauth1 import OAuth1ClientMixin
class Client(db.Model, OAuth1ClientMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
A client is registered by a user (developer) on your website. Get a deep
inside with ClientMixin
API reference.
A temporary credential is used to exchange a token credential. It is also known as “request token and secret”. Since it is temporary, it is better to save them into cache instead of database. A cache instance should has these methods:
.get(key)
.set(key, value, expires=None)
.delete(key)
A cache can be a memcache, redis or something else. If cache is not available, there is also a SQLAlchemy mixin:
from authlib.integrations.sqla_oauth1 import OAuth1TemporaryCredentialMixin
class TemporaryCredential(db.Model, OAuth1TemporaryCredentialMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
To make a Temporary Credentials model yourself, get more information with
ClientMixin
API reference.
A token credential is used to access resource owners’ resources. Unlike OAuth 2, the token credential will not expire in OAuth 1. This token credentials are supposed to be saved into a persist database rather than a cache.
Here is a SQLAlchemy mixin for easy integration:
from authlib.integrations.sqla_oauth1 import OAuth1TokenCredentialMixin
class TokenCredential(db.Model, OAuth1TokenCredentialMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
def set_user_id(self, user_id):
self.user_id = user_id
If SQLAlchemy is not what you want, read the API reference of
TokenCredentialMixin
and implement the missing
methods.
The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations. Authlib Flask integration has a built-in validation with cache.
If cache is not available, there is also a SQLAlchemy mixin:
from authlib.integrations.sqla_oauth1 import OAuth1TimestampNonceMixin
class TimestampNonce(db.Model, OAuth1TimestampNonceMixin)
id = db.Column(db.Integer, primary_key=True)
Authlib provides a ready to use
AuthorizationServer
which has built-in tools to handle requests and responses:
from authlib.integrations.flask_oauth1 import AuthorizationServer
from authlib.integrations.sqla_oauth1 import create_query_client_func
query_client = create_query_client_func(db.session, Client)
server = AuthorizationServer(app, query_client=query_client)
It can also be initialized lazily with init_app:
server = AuthorizationServer()
server.init_app(app, query_client=query_client)
It is strongly suggested that you use a cache. In this way, you don’t have to re-implement a lot of the missing methods.
There are other configurations. It works well without any changes. Here is a list of them:
OAUTH1_TOKEN_GENERATOR | A string of module path for importing a
function to generate oauth_token |
OAUTH1_TOKEN_SECRET_GENERATOR | A string of module path for importing a
function to generate oauth_token_secret . |
OAUTH1_TOKEN_LENGTH | If OAUTH1_TOKEN_GENERATOR is not
configured, a random function will generate
the given length of oauth_token . Default
value is 42 . |
OAUTH1_TOKEN_SECRET_LENGTH | A random function will generate the given
length of oauth_token_secret . Default
value is 48 . |
These configurations are used to create the token_generator
function. But
you can pass the token_generator
when initializing the AuthorizationServer:
def token_generator():
return {
'oauth_token': random_string(20),
'oauth_token_secret': random_string(46)
}
server = AuthorizationServer(
app,
query_client=query_client,
token_generator=token_generator
)
There are missing hooks that should be register_hook
to AuthorizationServer.
There are helper functions for registering hooks. If cache is available, you
can take the advantage with:
from authlib.integrations.flask_oauth1.cache import (
register_nonce_hooks,
register_temporary_credential_hooks
)
from authlib.integrations.sqla_oauth1 import register_token_credential_hooks
register_nonce_hooks(server, cache)
register_temporary_credential_hooks(server, cache)
register_token_credential_hooks(server, db.session, TokenCredential)
If cache is not available, here are the helpers for SQLAlchemy:
from authlib.integrations.sqla_oauth1 import (
register_nonce_hooks,
register_temporary_credential_hooks,
register_token_credential_hooks
)
register_nonce_hooks(server, db.session, TimestampNonce)
register_temporary_credential_hooks(server, db.session, TemporaryCredential)
register_token_credential_hooks(server, db.session, TokenCredential)
It is ready to create the endpoints for authorization and issuing tokens. Let’s start with the temporary credentials endpoint, which is used for clients to fetch a temporary credential:
@app.route('/initiate', methods=['POST'])
def initiate_temporary_credential():
return server.create_temporary_credentials_response()
The endpoint for resource owner authorization. OAuth 1 Client will redirect user to this authorization page, so that resource owner can grant or deny this request:
@app.route('/authorize', methods=['GET', 'POST'])
def authorize():
# make sure that user is logged in for yourself
if request.method == 'GET':
try:
req = server.check_authorization_request()
return render_template('authorize.html', req=req)
except OAuth1Error as error:
return render_template('error.html', error=error)
granted = request.form.get('granted')
if granted:
grant_user = current_user
else:
grant_user = None
try:
return server.create_authorization_response(grant_user=grant_user)
except OAuth1Error as error:
return render_template('error.html', error=error)
Then the final token endpoint. OAuth 1 Client will use the given temporary
credential and the oauth_verifier
authorized by resource owner to exchange
the token credential:
@app.route('/token', methods=['POST'])
def issue_token():
return server.create_token_response()
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
from authlib.integrations.flask_oauth1 import create_exists_nonce_func
from authlib.integrations.sqla_oauth1 import (
create_query_client_func,
create_query_token_func
)
query_client = create_query_client_func(db.session, Client)
query_token = create_query_token_func(db.session, TokenCredential)
exists_nonce = create_exists_nonce_func(cache)
# OR: authlib.integrations.sqla_oauth1.create_exists_nonce_func
require_oauth = ResourceProtector(
app, query_client=query_client,
query_token=query_token,
exists_nonce=exists_nonce,
)
# or initialize it lazily
require_oauth = ResourceProtector()
require_oauth.init_app(
app,
query_client=query_client,
query_token=query_token,
exists_nonce=exists_nonce,
)
@app.route('/user')
@require_oauth()
def user_profile():
user = current_credential.user
return jsonify(user)
The current_credential
is a proxy to the Token model you have defined above.
Since there is a user
relationship on the Token model, we can access this
user
with current_credential.user
.
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.
authlib.integrations.flask_oauth1.
AuthorizationServer
(app=None, query_client=None, token_generator=None)¶Flask implementation of authlib.rfc5849.AuthorizationServer
.
Initialize it with Flask app instance, client model class and cache:
server = AuthorizationServer(app=app, query_client=query_client)
# or initialize lazily
server = AuthorizationServer()
server.init_app(app, query_client=query_client)
Parameters: |
|
---|
Validate authorization request and create authorization response.
Assume the endpoint for authorization request is
https://photos.example.net/authorize
, the client redirects Jane’s
user-agent to the server’s Resource Owner Authorization endpoint to
obtain Jane’s approval for accessing her private photos:
https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola
The server requests Jane to sign in using her username and password and if successful, asks her to approve granting ‘printer.example.com’ access to her private photos. Jane approves the request and her user-agent is redirected to the callback URI provided by the client in the previous request (line breaks are for display purposes only):
http://printer.example.com/ready?
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884
Parameters: |
|
---|---|
Returns: | (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.get_user_id()
temporary_credential.user_id = user_id
temporary_credential.oauth_verifier = verifier
# if the credential has a save method
temporary_credential.save()
# remember to return the verifier
return verifier
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | A string of oauth_verifier |
create_temporary_credential
(request)¶Generate and save a temporary credential into database or cache. A temporary credential is used for exchanging token credential. This method should be re-implemented:
def create_temporary_credential(self, request):
oauth_token = generate_token(36)
oauth_token_secret = generate_token(48)
temporary_credential = TemporaryCredential(
oauth_token=oauth_token,
oauth_token_secret=oauth_token_secret,
client_id=request.client_id,
redirect_uri=request.redirect_uri,
)
# if the credential has a save method
temporary_credential.save()
return temporary_credential
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TemporaryCredential instance |
create_temporary_credentials_response
(request=None)¶Validate temporary credentials token request and create response
for temporary credentials token. Assume the endpoint of temporary
credentials request is https://photos.example.net/initiate
:
POST /initiate HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
oauth_consumer_key="dpf43f3p2l4k3l03",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="137131200",
oauth_nonce="wIjqoS",
oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"
The server validates the request and replies with a set of temporary credentials in the body of the HTTP response:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&
oauth_callback_confirmed=true
Parameters: | request – OAuth1Request instance. |
---|---|
Returns: | (status_code, body, headers) |
create_token_credential
(request)¶Create and save token credential into database. This method would be re-implemented like this:
def create_token_credential(self, request):
oauth_token = generate_token(36)
oauth_token_secret = generate_token(48)
temporary_credential = request.credential
token_credential = TokenCredential(
oauth_token=oauth_token,
oauth_token_secret=oauth_token_secret,
client_id=temporary_credential.get_client_id(),
user_id=temporary_credential.get_user_id()
)
# if the credential has a save method
token_credential.save()
return token_credential
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TokenCredential instance |
create_token_response
(request=None)¶Validate token request and create token response. Assuming the
endpoint of token request is https://photos.example.net/token
,
the callback request informs the client that Jane completed the
authorization process. The client then requests a set of token
credentials using its temporary credentials (over a secure Transport
Layer Security (TLS) channel):
POST /token HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
oauth_consumer_key="dpf43f3p2l4k3l03",
oauth_token="hh5s93j4hdidpola",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="137131201",
oauth_nonce="walatlh",
oauth_verifier="hfdp7dh39dks9884",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"
The server validates the request and replies with a set of token credentials in the body of the HTTP response:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00
Parameters: | request – OAuth1Request instance. |
---|---|
Returns: | (status_code, body, headers) |
delete_temporary_credential
(request)¶Delete temporary credential from database or cache. For instance, if temporary credential is saved in cache:
def delete_temporary_credential(self, request):
key = 'a-key-prefix:{}'.format(request.token)
cache.delete(key)
Parameters: | request – OAuth1Request instance |
---|
exists_nonce
(nonce, request)¶The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
Parameters: |
|
---|---|
Returns: | Boolean |
get_client_by_id
(client_id)¶Get client instance with the given client_id
.
Parameters: | client_id – A string of client_id |
---|---|
Returns: | Client instance |
get_temporary_credential
(request)¶Get the temporary credential from database or cache. A temporary
credential should share the same methods as described in models of
TemporaryCredentialMixin
:
def get_temporary_credential(self, request):
key = 'a-key-prefix:{}'.format(request.token)
data = cache.get(key)
# TemporaryCredential shares methods from TemporaryCredentialMixin
return TemporaryCredential(data)
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TemporaryCredential instance |
authlib.integrations.flask_oauth1.
ResourceProtector
(app=None, query_client=None, query_token=None, exists_nonce=None)¶A protecting method for resource servers. Initialize a resource protector with the query_token method:
from authlib.integrations.flask_oauth1 import ResourceProtector, current_credential
from authlib.integrations.flask_oauth1 import create_exists_nonce_func
from authlib.integrations.sqla_oauth1 import (
create_query_client_func,
create_query_token_func,
)
from your_project.models import Token, User, cache
# you need to define a ``cache`` instance yourself
require_oauth= ResourceProtector(
app,
query_client=create_query_client_func(db.session, OAuth1Client),
query_token=create_query_token_func(db.session, OAuth1Token),
exists_nonce=create_exists_nonce_func(cache)
)
# or initialize it lazily
require_oauth = ResourceProtector()
require_oauth.init_app(
app,
query_client=create_query_client_func(db.session, OAuth1Client),
query_token=create_query_token_func(db.session, OAuth1Token),
exists_nonce=create_exists_nonce_func(cache)
)
get_client_by_id
(client_id)¶Get client instance with the given client_id
.
Parameters: | client_id – A string of client_id |
---|---|
Returns: | Client instance |
get_token_credential
(request)¶Fetch the token credential from data store like a database, framework should implement this function.
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | Token model instance |
exists_nonce
(nonce, request)¶The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
Parameters: |
|
---|---|
Returns: | Boolean |
authlib.integrations.flask_oauth1.
current_credential
¶Routes protected by ResourceProtector
can access current credential
with this variable.
Warning
We will drop sqla_oauth2
module in version 1.0.
authlib.integrations.sqla_oauth1.
create_query_client_func
(session, model_class)¶Create an query_client
function that can be used in authorization
server and resource protector.
Parameters: |
|
---|
authlib.integrations.sqla_oauth1.
create_query_token_func
(session, model_class)¶Create an query_token
function that can be used in
resource protector.
Parameters: |
|
---|
authlib.integrations.sqla_oauth1.
create_exists_nonce_func
(session, model_class)¶Create an exists_nonce
function that can be used in hooks and
resource protector.
Parameters: |
|
---|
authlib.integrations.sqla_oauth1.
register_nonce_hooks
(authorization_server, session, model_class)¶Register nonce related hooks to authorization server.
Parameters: |
|
---|
authlib.integrations.sqla_oauth1.
register_temporary_credential_hooks
(authorization_server, session, model_class)¶Register temporary credential related hooks to authorization server.
Parameters: |
|
---|
authlib.integrations.sqla_oauth1.
register_token_credential_hooks
(authorization_server, session, model_class)¶Register token credential related hooks to authorization server.
Parameters: |
|
---|
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.
The Authorization Server provides several endpoints for authorization, issuing tokens, refreshing tokens and revoking tokens. When the resource owner (user) grants the authorization, this server will issue an access token to the client.
Before creating the authorization server, we need to understand several concepts:
Resource Owner is the user who is using your service. A resource owner can log in your website with username/email and password, or other methods.
A resource owner SHOULD implement get_user_id()
method, lets take
SQLAlchemy models for example:
class User(Model):
id = Column(Integer, primary_key=True)
# other columns
def get_user_id(self):
return self.id
A client is an application making protected resource requests on behalf of the resource owner and with its authorization. It contains at least three information:
Authlib has provided a mixin for SQLAlchemy, define the client with this mixin:
from authlib.integrations.sqla_oauth2 import OAuth2ClientMixin
class Client(Model, OAuth2ClientMixin):
id = Column(Integer, primary_key=True)
user_id = Column(
Integer, ForeignKey('user.id', ondelete='CASCADE')
)
user = relationship('User')
A client is registered by a user (developer) on your website. If you decide to
implement all the missing methods by yourself, get a deep inside with
ClientMixin
API reference.
Note
Only Bearer Token is supported by now. MAC Token is still under drafts, it will be available when it goes into RFC.
Tokens are used to access the users’ resources. A token is issued with a valid duration, limited scopes and etc. It contains at least:
With the SQLAlchemy mixin provided by Authlib:
from authlib.integrations.sqla_oauth2 import OAuth2TokenMixin
class Token(db.Model, OAuth2TokenMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
A token is associated with a resource owner. There is no certain name for
it, here we call it user
, but it can be anything else.
If you decide to implement all the missing methods by yourself, get a deep
inside with TokenMixin
API reference.
Authlib provides a ready to use
AuthorizationServer
which has built-in tools to handle requests and responses:
from authlib.integrations.flask_oauth2 import AuthorizationServer
def query_client(client_id):
return Client.query.filter_by(client_id=client_id).first()
def save_token(token_data, request):
if request.user:
user_id = request.user.get_user_id()
else:
# client_credentials grant_type
user_id = request.client.user_id
# or, depending on how you treat client_credentials
user_id = None
token = Token(
client_id=request.client.client_id,
user_id=user_id,
**token_data
)
db.session.add(token)
db.session.commit()
# or with the helper
from authlib.integrations.sqla_oauth2 import (
create_query_client_func,
create_save_token_func
)
query_client = create_query_client_func(db.session, Client)
save_token = create_save_token_func(db.session, Token)
server = AuthorizationServer(
app, query_client=query_client, save_token=save_token
)
It can also be initialized lazily with init_app:
server = AuthorizationServer()
server.init_app(app, query_client=query_client, save_token=save_token)
It works well without configuration. However, it can be configured with these settings:
OAUTH2_TOKEN_EXPIRES_IN | A dict to define expires_in for each grant |
OAUTH2_ACCESS_TOKEN_GENERATOR | A function or string of module path for importing
a function to generate access_token |
OAUTH2_REFRESH_TOKEN_GENERATOR | A function or string of module path for importing
a function to generate refresh_token . It can
also be True/False |
OAUTH2_ERROR_URIS | A list of tuple for (error , error_uri ) |
Hint
Here is an example of OAUTH2_TOKEN_EXPIRES_IN
:
OAUTH2_TOKEN_EXPIRES_IN = {
'authorization_code': 864000,
'implicit': 3600,
'password': 864000,
'client_credentials': 864000
}
Here is an example of OAUTH2_ACCESS_TOKEN_GENERATOR
:
def gen_access_token(client, grant_type, user, scope):
return create_some_random_string()
OAUTH2_REFRESH_TOKEN_GENERATOR
accepts the same parameters.
Now define an endpoint for authorization. This endpoint is used by
authorization_code
and implicit
grants:
from flask import request, render_template
from your_project.auth import current_user
@app.route('/oauth/authorize', methods=['GET', 'POST'])
def authorize():
# Login is required since we need to know the current resource owner.
# It can be done with a redirection to the login page, or a login
# form on this authorization page.
if request.method == 'GET':
grant = server.validate_consent_request(end_user=current_user)
return render_template(
'authorize.html',
grant=grant,
user=current_user,
)
confirmed = request.form['confirm']
if confirmed:
# granted by resource owner
return server.create_authorization_response(grant_user=current_user)
# denied by resource owner
return server.create_authorization_response(grant_user=None)
This is a simple demo, the real case should be more complex. There is a little more complex demo in https://github.com/authlib/example-oauth2-server.
The token endpoint is much easier:
@app.route('/oauth/token', methods=['POST'])
def issue_token():
return server.create_token_response()
However, the routes will not work properly. We need to register supported grants for them.
To create a better developer experience for debugging, it is suggested that you creating some documentation for errors. Here is a list of built-in Token Model.
You can design a documentation page with a description of each error. For
instance, there is a web page for invalid_client
:
https://developer.your-company.com/errors#invalid-client
In this case, you can register the error URI with OAUTH2_ERROR_URIS
configuration:
OAUTH2_ERROR_URIS = [
('invalid_client', 'https://developer.your-company.com/errors#invalid-client'),
# other error URIs
]
If there is no OAUTH2_ERROR_URIS
, the error response will not contain any
error_uri
data.
It is also possible to add i18n support to the error_description
. The
feature has been implemented in version 0.8, but there are still work to do.
There are four grant types defined by RFC6749, you can also create your own extended grant. Register the supported grant types to the authorization server.
Authorization Code Grant is a very common grant type, it is supported by almost every OAuth 2 providers. It uses an authorization code to exchange access token. In this case, we need a place to store the authorization code. It can be kept in a database or a cache like redis. Here is a SQLAlchemy mixin for AuthorizationCode:
from authlib.integrations.sqla_oauth2 import OAuth2AuthorizationCodeMixin
class AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
Implement this grant by subclass AuthorizationCodeGrant
:
from authlib.oauth2.rfc6749 import grants
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def save_authorization_code(self, code, request):
client = request.client
auth_code = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=request.user.id,
)
db.session.add(auth_code)
db.session.commit()
return auth_code
def query_authorization_code(self, code, client):
item = AuthorizationCode.query.filter_by(
code=code, client_id=client.client_id).first()
if item and not item.is_expired():
return item
def delete_authorization_code(self, authorization_code):
db.session.delete(authorization_code)
db.session.commit()
def authenticate_user(self, authorization_code):
return User.query.get(authorization_code.user_id)
# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant)
Note
AuthorizationCodeGrant is the most complex grant.
Default allowed Client Authentication Methods are:
You can change it in the subclass, e.g. remove the none
authentication method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post']
Note
This is important when you want to support OpenID Connect.
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
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 haven’t implemented refresh token endpoint. Authlib
provides it as a grant type, implement it with a subclass of
RefreshTokenGrant
:
from authlib.oauth2.rfc6749 import grants
class RefreshTokenGrant(grants.RefreshTokenGrant):
def authenticate_refresh_token(self, refresh_token):
item = Token.query.filter_by(refresh_token=refresh_token).first()
# define is_refresh_token_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:
response_type
.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.
Grant can accept extensions. Developers can pass extensions when registering grant:
authorization_server.register_grant(AuthorizationCodeGrant, [extension])
For instance, there is CodeChallenge
extension in Authlib:
server.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=False)])
Learn more about CodeChallenge
at RFC7636: Proof Key for Code Exchange by OAuth Public Clients.
Flask OAuth 2.0 authorization server has a method to register other token
endpoints: authorization_server.register_endpoint
. Find the available
endpoints:
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_oauth2 import ResourceProtector, current_token
from authlib.oauth2.rfc6750 import BearerTokenValidator
class MyBearerTokenValidator(BearerTokenValidator):
def authenticate_token(self, token_string):
return Token.query.filter_by(access_token=token_string).first()
def request_invalid(self, request):
return False
def token_revoked(self, token):
return token.revoked
require_oauth = ResourceProtector()
# only bearer token is supported currently
require_oauth.register_token_validator(MyBearerTokenValidator())
# you can also create BearerTokenValidator with shortcut
from authlib.integrations.sqla_oauth2 import create_bearer_token_validator
BearerTokenValidator = create_bearer_token_validator(db.session, Token)
require_oauth.register_token_validator(BearerTokenValidator())
@app.route('/user')
@require_oauth('profile')
def user_profile():
user = current_token.user
return jsonify(user)
If the resource is not protected by a scope, use None
:
@app.route('/user')
@require_oauth()
def user_profile():
user = current_token.user
return jsonify(user)
# or with None
@app.route('/user')
@require_oauth(None)
def user_profile():
user = current_token.user
return jsonify(user)
The current_token
is a proxy to the Token model you have defined above.
Since there is a user
relationship on the Token model, we can access this
user
with current_token.user
.
If decorator is not your favorite, there is a with
statement for you:
@app.route('/user')
def user_profile():
with require_oauth.acquire('profile') as token:
user = token.user
return jsonify(user)
You can apply multiple scopes to one endpoint in AND and OR modes. The default is AND mode.
@app.route('/profile')
@require_oauth('profile email', 'AND')
def user_profile():
user = current_token.user
return jsonify(user)
It requires the token containing both profile
and email
scope.
@app.route('/profile')
@require_oauth('profile email', 'OR')
def user_profile():
user = current_token.user
return jsonify(user)
It requires the token containing either profile
or email
scope.
It is also possible to pass a function as the scope operator. e.g.:
def scope_operator(token_scopes, resource_scopes):
# this equals "AND"
return token_scopes.issuperset(resource_scopes)
@app.route('/profile')
@require_oauth('profile email', scope_operator)
def user_profile():
user = current_token.user
return jsonify(user)
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 lease four concepts:
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:
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.
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 AuthorizationCode
data,
we need to save this value into 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 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):
exists = AuthorizationCode.query.filter_by(
client_id=request.client_id, nonce=nonce
).first()
return bool(exists)
def get_jwt_config(self):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'https://example.com',
'exp': 3600
}
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
server.register_grant(OpenIDImplicitGrant)
Hybrid flow 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
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 requires 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.
authlib.integrations.flask_oauth2.
AuthorizationServer
(app=None, query_client=None, save_token=None)¶Flask implementation of authlib.oauth2.rfc6749.AuthorizationServer
.
Initialize it with query_client
, save_token
methods and Flask
app instance:
def query_client(client_id):
return Client.query.filter_by(client_id=client_id).first()
def save_token(token, request):
if request.user:
user_id = request.user.get_user_id()
else:
user_id = None
client = request.client
tok = Token(
client_id=client.client_id,
user_id=user.get_user_id(),
**token
)
db.session.add(tok)
db.session.commit()
server = AuthorizationServer(app, query_client, save_token)
# or initialize lazily
server = AuthorizationServer()
server.init_app(app, query_client, save_token)
Validate authorization request and create authorization response.
Parameters: |
|
---|---|
Returns: | Response |
create_bearer_token_generator
(config)¶Create a generator function for generating token
value. This
method will create a Bearer Token generator with
authlib.oauth2.rfc6750.BearerToken
. By default, it will not
generate refresh_token
, which can be turn on by configuration
OAUTH2_REFRESH_TOKEN_GENERATOR=True
.
create_endpoint_response
(name, request=None)¶Validate endpoint request and create endpoint response.
Parameters: |
|
---|---|
Returns: | Response |
create_token_expires_in_generator
(config)¶Create a generator function for generating expires_in
value.
Developers can re-implement this method with a subclass if other means
required. The default expires_in value is defined by grant_type
,
different grant_type
has different value. It can be configured
with:
OAUTH2_TOKEN_EXPIRES_IN = {
'authorization_code': 864000,
'urn:ietf:params:oauth:grant-type:jwt-bearer': 3600,
}
create_token_response
(request=None)¶Validate token request and create token response.
Parameters: | request – HTTP request instance |
---|
register_endpoint
(endpoint_cls)¶Add extra endpoint to authorization server. e.g. RevocationEndpoint:
authorization_server.register_endpoint(RevocationEndpoint)
Parameters: | endpoint_cls – A endpoint class |
---|
register_grant
(grant_cls, extensions=None)¶Register a grant class into the endpoint registry. Developers
can implement the grants in authlib.oauth2.rfc6749.grants
and
register with this method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def authenticate_user(self, credential):
# ...
authorization_server.register_grant(AuthorizationCodeGrant)
Parameters: |
|
---|
validate_consent_request
(request=None, end_user=None)¶Validate current HTTP request for authorization page. This page is designed for resource owner to grant or deny the authorization:
@app.route('/authorize', methods=['GET'])
def authorize():
try:
grant = server.validate_consent_request(end_user=current_user)
return render_template(
'authorize.html',
grant=grant,
user=current_user
)
except OAuth2Error as error:
return render_template(
'error.html',
error=error
)
authlib.integrations.flask_oauth2.
ResourceProtector
¶A protecting method for resource servers. Creating a require_oauth
decorator easily with ResourceProtector:
from authlib.integrations.flask_oauth2 import ResourceProtector
require_oauth = ResourceProtector()
# add bearer token validator
from authlib.oauth2.rfc6750 import BearerTokenValidator
from project.models import Token
class MyBearerTokenValidator(BearerTokenValidator):
def authenticate_token(self, token_string):
return Token.query.filter_by(access_token=token_string).first()
def request_invalid(self, request):
return False
def token_revoked(self, token):
return False
require_oauth.register_token_validator(MyBearerTokenValidator())
# protect resource with require_oauth
@app.route('/user')
@require_oauth('profile')
def user_profile():
user = User.query.get(current_token.user_id)
return jsonify(user.to_dict())
raise_error_response
(error)¶Raise HTTPException for OAuth2Error. Developers can re-implement this method to customize the error response.
Parameters: | error – OAuth2Error |
---|---|
Raise: | HTTPException |
acquire_token
(scope=None, operator='AND')¶A method to acquire current valid token with the given scope.
Parameters: |
|
---|---|
Returns: | token object |
acquire
(scope=None, operator='AND')¶The with statement of require_oauth
. Instead of using a
decorator, you can use a with statement instead:
@app.route('/api/user')
def user_api():
with require_oauth.acquire('profile') as token:
user = User.query.get(token.user_id)
return jsonify(user.to_dict())
authlib.integrations.flask_oauth2.
current_token
¶Routes protected by ResourceProtector
can access current token
with this variable:
from authlib.integrations.flask_oauth2 import current_token
@require_oauth()
@app.route('/user_id')
def user_id():
# current token instance of the OAuth Token model
return current_token.user_id
authlib.integrations.flask_oauth2.
client_authenticated
¶Signal when client is authenticated
authlib.integrations.flask_oauth2.
token_revoked
¶Signal when token is revoked
authlib.integrations.flask_oauth2.
token_authenticated
¶Signal when token is authenticated
Warning
We will drop sqla_oauth2
module in version 1.0.
authlib.integrations.sqla_oauth2.
create_query_client_func
(session, client_model)¶Create an query_client
function that can be used in authorization
server.
Parameters: |
|
---|
authlib.integrations.sqla_oauth2.
create_save_token_func
(session, token_model)¶Create an save_token
function that can be used in authorization
server.
Parameters: |
|
---|
authlib.integrations.sqla_oauth2.
create_query_token_func
(session, token_model)¶Create an query_token
function for revocation, introspection
token endpoints.
Parameters: |
|
---|
authlib.integrations.sqla_oauth2.
create_revocation_endpoint
(session, token_model)¶Create a revocation endpoint class with SQLAlchemy session and token model.
Parameters: |
|
---|
authlib.integrations.sqla_oauth2.
create_bearer_token_validator
(session, token_model)¶Create an bearer token validator class with SQLAlchemy session and token model.
Parameters: |
|
---|
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:
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.
The Authorization Server provides several endpoints for temporary credentials, authorization, and issuing token credentials. When the resource owner (user) grants the authorization, this server will issue a token credential to the client.
Currently, Authlib Django implementation is using cache a lot, which means you don’t have to handle temporary credentials, timestamp and nonce yourself, they are all built-in.
To create an authorization server, only Client and Token models are required:
from your_project.models import Client, Token
from authlib.integrations.django_oauth1 import CacheAuthorizationServer
authorization_server = CacheAuthorizationServer(Client, Token)
Resource Owner is the user who is using your service. A resource owner can log in your website with username/email and password, or other methods. In Django, we can use the built in contrib user:
from django.contrib.auth.models import User
A client is an application making protected resource requests on behalf of the resource owner and with its authorization. It contains at least three information:
Authlib has no implementation for client model in Django. You need to implement it yourself:
from django.db import models
from django.contrib.auth.models import User
from authlib.oauth1 import ClientMixin
class Client(models.Model, ClientMixin):
user = models.ForeignKey(User, on_delete=CASCADE)
client_id = models.CharField(max_length=48, unique=True, db_index=True)
client_secret = models.CharField(max_length=48, blank=True)
default_redirect_uri = models.TextField(blank=False, default='')
def get_default_redirect_uri(self):
return self.default_redirect_uri
def get_client_secret(self):
return self.client_secret
def get_rsa_public_key(self):
return None
A client is registered by a user (developer) on your website. Get a deep
inside with ClientMixin
API reference.
A token credential is used to access resource owners’ resources. Unlike OAuth 2, the token credential will not expire in OAuth 1. This token credentials are supposed to be saved into a persist database rather than a cache.
Here is an example of how it looks in Django:
from django.db import models
from django.contrib.auth.models import User
from authlib.oauth1 import TokenCredentialMixin
class Token(models.Model, TokenCredentialMixin):
user = models.ForeignKey(User, on_delete=CASCADE)
client_id = models.CharField(max_length=48, db_index=True)
oauth_token = models.CharField(max_length=84, unique=True, db_index=True)
oauth_token_secret = models.CharField(max_length=84)
def get_oauth_token(self):
return self.oauth_token
def get_oauth_token_secret(self):
return self.oauth_token_secret
It is ready to create the endpoints for authorization and issuing tokens. Let’s start with the temporary credentials endpoint, which is used for clients to fetch a temporary credential:
from django.views.decorators.http import require_http_methods
@require_http_methods(["POST"])
def initiate_temporary_credential(request):
return server.create_temporary_credential_response(request)
The endpoint for resource owner authorization. OAuth 1 Client will redirect user to this authorization page, so that resource owner can grant or deny this request:
from django.shortcuts import render
def authorize(request):
# make sure that user is logged in for yourself
if request.method == 'GET':
try:
req = server.check_authorization_request(request)
context = {'req': req}
return render(request, 'authorize.html', context)
except OAuth1Error as error:
context = {'error': error}
return render(request, 'error.html', context)
granted = request.POST.get('granted')
if granted:
grant_user = request.user
else:
grant_user = None
try:
return server.create_authorization_response(request, grant_user)
except OAuth1Error as error:
context = {'error': error}
return render(request, 'error.html', context)
Then the final token endpoint. OAuth 1 Client will use the given temporary
credential and the oauth_verifier
authorized by resource owner to exchange
the token credential:
from django.views.decorators.http import require_http_methods
@require_http_methods(["POST"])
def issue_token(request):
return server.create_token_response(request)
At last, you need to register these views into url patterns.
Protect 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.
authlib.integrations.django_oauth1.
CacheAuthorizationServer
(client_model, token_model, token_generator=None)¶Validate authorization request and create authorization response.
Assume the endpoint for authorization request is
https://photos.example.net/authorize
, the client redirects Jane’s
user-agent to the server’s Resource Owner Authorization endpoint to
obtain Jane’s approval for accessing her private photos:
https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola
The server requests Jane to sign in using her username and password and if successful, asks her to approve granting ‘printer.example.com’ access to her private photos. Jane approves the request and her user-agent is redirected to the callback URI provided by the client in the previous request (line breaks are for display purposes only):
http://printer.example.com/ready?
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884
Parameters: |
|
---|---|
Returns: | (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.get_user_id()
temporary_credential.user_id = user_id
temporary_credential.oauth_verifier = verifier
# if the credential has a save method
temporary_credential.save()
# remember to return the verifier
return verifier
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | A string of oauth_verifier |
create_temporary_credential
(request)¶Generate and save a temporary credential into database or cache. A temporary credential is used for exchanging token credential. This method should be re-implemented:
def create_temporary_credential(self, request):
oauth_token = generate_token(36)
oauth_token_secret = generate_token(48)
temporary_credential = TemporaryCredential(
oauth_token=oauth_token,
oauth_token_secret=oauth_token_secret,
client_id=request.client_id,
redirect_uri=request.redirect_uri,
)
# if the credential has a save method
temporary_credential.save()
return temporary_credential
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TemporaryCredential instance |
create_temporary_credentials_response
(request=None)¶Validate temporary credentials token request and create response
for temporary credentials token. Assume the endpoint of temporary
credentials request is https://photos.example.net/initiate
:
POST /initiate HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
oauth_consumer_key="dpf43f3p2l4k3l03",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="137131200",
oauth_nonce="wIjqoS",
oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"
The server validates the request and replies with a set of temporary credentials in the body of the HTTP response:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&
oauth_callback_confirmed=true
Parameters: | request – OAuth1Request instance. |
---|---|
Returns: | (status_code, body, headers) |
create_token_credential
(request)¶Create and save token credential into database. This method would be re-implemented like this:
def create_token_credential(self, request):
oauth_token = generate_token(36)
oauth_token_secret = generate_token(48)
temporary_credential = request.credential
token_credential = TokenCredential(
oauth_token=oauth_token,
oauth_token_secret=oauth_token_secret,
client_id=temporary_credential.get_client_id(),
user_id=temporary_credential.get_user_id()
)
# if the credential has a save method
token_credential.save()
return token_credential
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TokenCredential instance |
create_token_response
(request)¶Validate token request and create token response. Assuming the
endpoint of token request is https://photos.example.net/token
,
the callback request informs the client that Jane completed the
authorization process. The client then requests a set of token
credentials using its temporary credentials (over a secure Transport
Layer Security (TLS) channel):
POST /token HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
oauth_consumer_key="dpf43f3p2l4k3l03",
oauth_token="hh5s93j4hdidpola",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="137131201",
oauth_nonce="walatlh",
oauth_verifier="hfdp7dh39dks9884",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"
The server validates the request and replies with a set of token credentials in the body of the HTTP response:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00
Parameters: | request – OAuth1Request instance. |
---|---|
Returns: | (status_code, body, headers) |
delete_temporary_credential
(request)¶Delete temporary credential from database or cache. For instance, if temporary credential is saved in cache:
def delete_temporary_credential(self, request):
key = 'a-key-prefix:{}'.format(request.token)
cache.delete(key)
Parameters: | request – OAuth1Request instance |
---|
exists_nonce
(nonce, request)¶The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
Parameters: |
|
---|---|
Returns: | Boolean |
get_client_by_id
(client_id)¶Get client instance with the given client_id
.
Parameters: | client_id – A string of client_id |
---|---|
Returns: | Client instance |
get_temporary_credential
(request)¶Get the temporary credential from database or cache. A temporary
credential should share the same methods as described in models of
TemporaryCredentialMixin
:
def get_temporary_credential(self, request):
key = 'a-key-prefix:{}'.format(request.token)
data = cache.get(key)
# TemporaryCredential shares methods from TemporaryCredentialMixin
return TemporaryCredential(data)
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TemporaryCredential instance |
register_signature_method
(name, verify)¶Extend signature method verification.
Parameters: |
|
---|
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
(request)¶Validate oauth_signature
from HTTP request.
Parameters: | request – OAuth1Request instance |
---|
validate_temporary_credentials_request
(request)¶Validate HTTP request for temporary credentials.
validate_timestamp_and_nonce
(request)¶Validate oauth_timestamp
and oauth_nonce
in HTTP request.
Parameters: | request – OAuth1Request instance |
---|
validate_token_request
(request)¶Validate request for issuing token.
authlib.integrations.django_oauth1.
ResourceProtector
(client_model, token_model)¶get_client_by_id
(client_id)¶Get client instance with the given client_id
.
Parameters: | client_id – A string of client_id |
---|---|
Returns: | Client instance |
get_token_credential
(request)¶Fetch the token credential from data store like a database, framework should implement this function.
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | Token model instance |
exists_nonce
(nonce, request)¶The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
Parameters: |
|
---|---|
Returns: | 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.
The Authorization Server provides several endpoints for authorization, issuing tokens, refreshing tokens and revoking tokens. When the resource owner (user) grants the authorization, this server will issue an access token to the client.
Before creating the authorization server, we need to understand several concepts:
Resource Owner is the user who is using your service. A resource owner can log in your website with username/email and password, or other methods.
In this documentation, we will use the django.contrib.auth.models.User
as
an example.
A client is an application making protected resource requests on behalf of the resource owner and with its authorization. It contains at least three information:
A client is registered by a user (developer) on your website; you MUST implement
the missing methods of ClientMixin
:
class OAuth2Client(Model, ClientMixin):
user = ForeignKey(User, on_delete=CASCADE)
client_id = CharField(max_length=48, unique=True, db_index=True)
client_secret = CharField(max_length=48, blank=True)
client_name = CharField(max_length=120)
redirect_uris = TextField(default='')
default_redirect_uri = TextField(blank=False, default='')
scope = TextField(default='')
response_type = TextField(default='')
grant_type = TextField(default='')
token_endpoint_auth_method = CharField(max_length=120, default='')
# you can add more fields according to your own need
# check https://tools.ietf.org/html/rfc7591#section-2
def get_client_id(self):
return self.client_id
def get_default_redirect_uri(self):
return self.default_redirect_uri
def get_allowed_scope(self, scope):
if not scope:
return ''
allowed = set(scope_to_list(self.scope))
return list_to_scope([s for s in scope.split() if s in allowed])
def check_redirect_uri(self, redirect_uri):
if redirect_uri == self.default_redirect_uri:
return True
return redirect_uri in self.redirect_uris
def has_client_secret(self):
return bool(self.client_secret)
def check_client_secret(self, client_secret):
return self.client_secret == client_secret
def check_token_endpoint_auth_method(self, method):
return self.token_endpoint_auth_method == method
def check_response_type(self, response_type):
allowed = self.response_type.split()
return response_type in allowed
def check_grant_type(self, grant_type):
allowed = self.grant_type.split()
return grant_type in allowed
Tokens are used to access the users’ resources. A token is issued with a valid duration, limited scopes and etc. It contains at least:
A token is associated with a resource owner; you MUST implement
the missing methods of TokenMixin
:
import time
def now_timestamp():
return int(time.time())
class OAuth2Token(Model, TokenMixin):
user = ForeignKey(User, on_delete=CASCADE)
client_id = CharField(max_length=48, db_index=True)
token_type = CharField(max_length=40)
access_token = CharField(max_length=255, unique=True, null=False)
refresh_token = CharField(max_length=255, db_index=True)
scope = TextField(default='')
revoked = BooleanField(default=False)
issued_at = IntegerField(null=False, default=now_timestamp)
expires_in = IntegerField(null=False, default=0)
def get_client_id(self):
return self.client_id
def get_scope(self):
return self.scope
def get_expires_in(self):
return self.expires_in
def get_expires_at(self):
return self.issued_at + self.expires_in
Authlib provides a ready to use AuthorizationServer
which has built-in tools to handle requests and responses:
from authlib.integrations.django_oauth2 import AuthorizationServer
server = AuthorizationServer(OAuth2Client, OAuth2Token)
The Authorization Server has to provide endpoints:
authorization_code
or implicit
grant typesThe AuthorizationServer
has provided built-in methods to handle these endpoints:
from django.shortcuts import render
from django.views.decorators.http import require_http_methods
# use ``server.create_authorization_response`` to handle authorization endpoint
def authorize(request):
if request.method == 'GET':
grant = server.validate_consent_request(request, end_user=request.user)
context = dict(grant=grant, user=request.user)
return render(request, 'authorize.html', context)
if is_user_confirmed(request):
# granted by resource owner
return server.create_authorization_response(request, grant_user=request.user)
# denied by resource owner
return server.create_authorization_response(request, grant_user=None)
# use ``server.create_token_response`` to handle token endpoint
@require_http_methods(["POST"]) # we only allow POST for token endpoint
def issue_token(request):
return server.create_token_response(request)
For now, you have set up the authorization server. But it won’t work since it doesn’t support any grant types yet. Let’s head over to the next chapter.
There are four grant types defined by RFC6749, you can also create your own extended grant. Register the supported grant types to the authorization server.
Authorization Code Grant is a very common grant type, it is supported by almost every OAuth 2 providers. It uses an authorization code to exchange access token. In this case, we need a place to store the authorization code. It can be kept in a database or a cache like redis. Here is an example of database AuthorizationCode:
from django.db.models import ForeignKey, CASCADE
from django.contrib.auth.models import User
from authlib.oauth2.rfc6749 import AuthorizationCodeMixin
def now_timestamp():
return int(time.time())
class AuthorizationCode(Model, AuthorizationCodeMixin):
user = ForeignKey(User, on_delete=CASCADE)
client_id = CharField(max_length=48, db_index=True)
code = CharField(max_length=120, unique=True, null=False)
redirect_uri = TextField(default='', null=True)
response_type = TextField(default='')
scope = TextField(default='', null=True)
auth_time = IntegerField(null=False, default=now_timestamp)
def is_expired(self):
return self.auth_time + 300 < time.time()
def get_redirect_uri(self):
return self.redirect_uri
def get_scope(self):
return self.scope or ''
def get_auth_time(self):
return self.auth_time
Note here, you MUST implement the missing methods of
AuthorizationCodeMixin
API interface.
Later, you can use this AuthorizationCode
database model to handle authorization_code
grant type. Here is how:
from authlib.oauth2.rfc6749 import grants
from authlib.common.security import generate_token
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def save_authorization_code(self, code, request):
client = request.client
auth_code = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
response_type=request.response_type,
scope=request.scope,
user=request.user,
)
auth_code.save()
return auth_code
def query_authorization_code(self, code, client):
try:
item = OAuth2Code.objects.get(code=code, client_id=client.client_id)
except OAuth2Code.DoesNotExist:
return None
if not item.is_expired():
return item
def delete_authorization_code(self, authorization_code):
authorization_code.delete()
def authenticate_user(self, authorization_code):
return authorization_code.user
# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant)
Note
AuthorizationCodeGrant is the most complex grant.
Default allowed Client Authentication Methods are:
You can change it in the subclass, e.g. remove the none
authentication method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post']
Note
This is important when you want to support OpenID Connect.
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:
response_type
.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:
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.
You can apply multiple scopes to one endpoint in AND and OR modes. The default is AND mode.
@require_oauth('profile email', 'AND')
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', 'OR')
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 pass a function as the scope operator. e.g.:
def scope_operator(token_scopes, resource_scopes):
# this equals "AND"
return token_scopes.issuperset(resource_scopes)
@require_oauth('profile email', scope_operator)
def user_profile(request):
user = request.oauth_token.user
return JsonResponse(dict(sub=user.pk, username=user.username))
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:
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:
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.
authlib.integrations.django_oauth2.
AuthorizationServer
(client_model, token_model, generate_token=None, metadata=None)¶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.
Parameters: |
|
---|---|
Returns: | Response |
create_endpoint_response
(name, request=None)¶Validate endpoint request and create endpoint response.
Parameters: |
|
---|---|
Returns: | Response |
create_token_response
(request=None)¶Validate token request and create token response.
Parameters: | request – HTTP request instance |
---|
register_endpoint
(endpoint_cls)¶Add extra endpoint to authorization server. e.g. RevocationEndpoint:
authorization_server.register_endpoint(RevocationEndpoint)
Parameters: | endpoint_cls – A endpoint class |
---|
register_grant
(grant_cls, extensions=None)¶Register a grant class into the endpoint registry. Developers
can implement the grants in authlib.oauth2.rfc6749.grants
and
register with this method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def authenticate_user(self, credential):
# ...
authorization_server.register_grant(AuthorizationCodeGrant)
Parameters: |
|
---|
authlib.integrations.django_oauth2.
ResourceProtector
¶acquire_token
(request, scope=None, operator='AND')¶A method to acquire current valid token with the given scope.
Parameters: |
|
---|---|
Returns: | token object |
authlib.integrations.django_oauth2.
BearerTokenValidator
(token_model, realm=None)¶authenticate_token
(token_string)¶A method to query token from database with the given token string. Developers MUST re-implement this method. For instance:
def authenticate_token(self, token_string):
return get_token_from_database(token_string)
Parameters: | token_string – A string to represent the access_token. |
---|---|
Returns: | token |
request_invalid
(request)¶Check if the HTTP request is valid or not. Developers MUST re-implement this method. For instance, your server requires a “X-Device-Version” in the header:
def request_invalid(self, request):
return 'X-Device-Version' in request.headers
Usually, you don’t have to detect if the request is valid or not,
you can just return a False
.
Parameters: | request – instance of HttpRequest |
---|---|
Returns: | Boolean |
token_revoked
(token)¶Check if this token is revoked. Developers MUST re-implement this
method. If there is a column called revoked
on the token table:
def token_revoked(self, token):
return token.revoked
Parameters: | token – token instance |
---|---|
Returns: | Boolean |
authlib.integrations.django_oauth2.
RevocationEndpoint
(server)¶The revocation endpoint for OAuth authorization servers allows clients to notify the authorization server that a previously obtained refresh or access token is no longer needed.
Register it into authorization server, and create token endpoint response for token revocation:
from django.views.decorators.http import require_http_methods
# see register into authorization server instance
server.register_endpoint(RevocationEndpoint)
@require_http_methods(["POST"])
def revoke_token(request):
return server.create_endpoint_response(
RevocationEndpoint.ENDPOINT_NAME,
request
)
query_token
(token, token_type_hint, client)¶Query requested token from database.
revoke_token
(token)¶Mark the give token as revoked.
authlib.integrations.django_oauth2.
client_authenticated
¶Signal when client is authenticated
authlib.integrations.django_oauth2.
token_revoked
¶Signal when token is revoked
authlib.integrations.django_oauth2.
token_authenticated
¶Signal when token is authenticated
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:
authlib.oauth1.rfc5849.
AuthorizationServer
¶Validate authorization request and create authorization response.
Assume the endpoint for authorization request is
https://photos.example.net/authorize
, the client redirects Jane’s
user-agent to the server’s Resource Owner Authorization endpoint to
obtain Jane’s approval for accessing her private photos:
https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola
The server requests Jane to sign in using her username and password and if successful, asks her to approve granting ‘printer.example.com’ access to her private photos. Jane approves the request and her user-agent is redirected to the callback URI provided by the client in the previous request (line breaks are for display purposes only):
http://printer.example.com/ready?
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884
Parameters: |
|
---|---|
Returns: | (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.get_user_id()
temporary_credential.user_id = user_id
temporary_credential.oauth_verifier = verifier
# if the credential has a save method
temporary_credential.save()
# remember to return the verifier
return verifier
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | A string of oauth_verifier |
create_temporary_credential
(request)¶Generate and save a temporary credential into database or cache. A temporary credential is used for exchanging token credential. This method should be re-implemented:
def create_temporary_credential(self, request):
oauth_token = generate_token(36)
oauth_token_secret = generate_token(48)
temporary_credential = TemporaryCredential(
oauth_token=oauth_token,
oauth_token_secret=oauth_token_secret,
client_id=request.client_id,
redirect_uri=request.redirect_uri,
)
# if the credential has a save method
temporary_credential.save()
return temporary_credential
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TemporaryCredential instance |
create_temporary_credentials_response
(request=None)¶Validate temporary credentials token request and create response
for temporary credentials token. Assume the endpoint of temporary
credentials request is https://photos.example.net/initiate
:
POST /initiate HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
oauth_consumer_key="dpf43f3p2l4k3l03",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="137131200",
oauth_nonce="wIjqoS",
oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"
The server validates the request and replies with a set of temporary credentials in the body of the HTTP response:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&
oauth_callback_confirmed=true
Parameters: | request – OAuth1Request instance. |
---|---|
Returns: | (status_code, body, headers) |
create_token_credential
(request)¶Create and save token credential into database. This method would be re-implemented like this:
def create_token_credential(self, request):
oauth_token = generate_token(36)
oauth_token_secret = generate_token(48)
temporary_credential = request.credential
token_credential = TokenCredential(
oauth_token=oauth_token,
oauth_token_secret=oauth_token_secret,
client_id=temporary_credential.get_client_id(),
user_id=temporary_credential.get_user_id()
)
# if the credential has a save method
token_credential.save()
return token_credential
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TokenCredential instance |
create_token_response
(request)¶Validate token request and create token response. Assuming the
endpoint of token request is https://photos.example.net/token
,
the callback request informs the client that Jane completed the
authorization process. The client then requests a set of token
credentials using its temporary credentials (over a secure Transport
Layer Security (TLS) channel):
POST /token HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
oauth_consumer_key="dpf43f3p2l4k3l03",
oauth_token="hh5s93j4hdidpola",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="137131201",
oauth_nonce="walatlh",
oauth_verifier="hfdp7dh39dks9884",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"
The server validates the request and replies with a set of token credentials in the body of the HTTP response:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00
Parameters: | request – OAuth1Request instance. |
---|---|
Returns: | (status_code, body, headers) |
delete_temporary_credential
(request)¶Delete temporary credential from database or cache. For instance, if temporary credential is saved in cache:
def delete_temporary_credential(self, request):
key = 'a-key-prefix:{}'.format(request.token)
cache.delete(key)
Parameters: | request – OAuth1Request instance |
---|
exists_nonce
(nonce, request)¶The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
Parameters: |
|
---|---|
Returns: | Boolean |
get_client_by_id
(client_id)¶Get client instance with the given client_id
.
Parameters: | client_id – A string of client_id |
---|---|
Returns: | Client instance |
get_temporary_credential
(request)¶Get the temporary credential from database or cache. A temporary
credential should share the same methods as described in models of
TemporaryCredentialMixin
:
def get_temporary_credential(self, request):
key = 'a-key-prefix:{}'.format(request.token)
data = cache.get(key)
# TemporaryCredential shares methods from TemporaryCredentialMixin
return TemporaryCredential(data)
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | TemporaryCredential instance |
register_signature_method
(name, verify)¶Extend signature method verification.
Parameters: |
|
---|
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
(request)¶Validate oauth_signature
from HTTP request.
Parameters: | request – OAuth1Request instance |
---|
validate_temporary_credentials_request
(request)¶Validate HTTP request for temporary credentials.
validate_timestamp_and_nonce
(request)¶Validate oauth_timestamp
and oauth_nonce
in HTTP request.
Parameters: | request – OAuth1Request instance |
---|
validate_token_request
(request)¶Validate request for issuing token.
authlib.oauth1.rfc5849.
ResourceProtector
¶exists_nonce
(nonce, request)¶The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
Parameters: |
|
---|---|
Returns: | Boolean |
get_client_by_id
(client_id)¶Get client instance with the given client_id
.
Parameters: | client_id – A string of client_id |
---|---|
Returns: | Client instance |
get_token_credential
(request)¶Fetch the token credential from data store like a database, framework should implement this function.
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | Token model instance |
register_signature_method
(name, verify)¶Extend signature method verification.
Parameters: |
|
---|
The verify
method accept OAuth1Request
as parameter:
def verify_custom_method(request):
# verify this request, return True or False
return True
Server.register_signature_method('custom-name', verify_custom_method)
validate_oauth_signature
(request)¶Validate oauth_signature
from HTTP request.
Parameters: | request – OAuth1Request instance |
---|
validate_timestamp_and_nonce
(request)¶Validate oauth_timestamp
and oauth_nonce
in HTTP request.
Parameters: | request – OAuth1Request instance |
---|
authlib.oauth1.rfc5849.
ClientMixin
¶get_client_secret
()¶A method to return the client_secret of this client. For instance,
the database table has a column called client_secret
:
def get_client_secret(self):
return self.client_secret
get_default_redirect_uri
()¶A method to get client default redirect_uri. For instance, the
database table for client has a column called default_redirect_uri
:
def get_default_redirect_uri(self):
return self.default_redirect_uri
Returns: | A URL string |
---|
get_rsa_public_key
()¶A method to get the RSA public key for RSA-SHA1 signature method.
For instance, the value is saved on column rsa_public_key
:
def get_rsa_public_key(self):
return self.rsa_public_key
authlib.oauth1.rfc5849.
TemporaryCredentialMixin
¶check_verifier
(verifier)¶A method to check if the given verifier matches this temporary
credential. For instance that this temporary credential has recorded
the value in database as column oauth_verifier
:
def check_verifier(self, verifier):
return self.oauth_verifier == verifier
Returns: | Boolean |
---|
get_client_id
()¶A method to get the client_id associated with this credential.
For instance, the table in the database has a column client_id
:
def get_client_id(self):
return self.client_id
get_oauth_token
()¶A method to get the value of oauth_token
. For instance, the
database table has a column called oauth_token
:
def get_oauth_token(self):
return self.oauth_token
Returns: | A string |
---|
get_oauth_token_secret
()¶A method to get the value of oauth_token_secret
. For instance,
the database table has a column called oauth_token_secret
:
def get_oauth_token_secret(self):
return self.oauth_token_secret
Returns: | A string |
---|
get_redirect_uri
()¶A method to get temporary credential’s oauth_callback
.
For instance, the database table for temporary credential has a
column called oauth_callback
:
def get_redirect_uri(self):
return self.oauth_callback
Returns: | A URL string |
---|
authlib.oauth1.rfc5849.
TokenCredentialMixin
¶get_oauth_token
()¶A method to get the value of oauth_token
. For instance, the
database table has a column called oauth_token
:
def get_oauth_token(self):
return self.oauth_token
Returns: | A string |
---|
get_oauth_token_secret
()¶A method to get the value of oauth_token_secret
. For instance,
the database table has a column called oauth_token_secret
:
def get_oauth_token_secret(self):
return self.oauth_token_secret
Returns: | A string |
---|
This section contains the generic implementation of RFC6749. You should read Introduce OAuth 2.0 at first. Here are some tips:
Here are the API references for developers. For framework level interfaces, check:
authlib.oauth2.rfc6749.
AuthorizationServer
(query_client, save_token, generate_token=None, metadata=None)¶Authorization server that handles Authorization Endpoint and Token Endpoint.
Parameters: |
|
---|
authenticate_client
(request, methods)¶Authenticate client via HTTP request information with the given
methods, such as client_secret_basic
, client_secret_post
.
Validate authorization request and create authorization response.
Parameters: |
|
---|---|
Returns: | Response |
create_endpoint_response
(name, request=None)¶Validate endpoint request and create endpoint response.
Parameters: |
|
---|---|
Returns: | Response |
create_json_request
(request)¶This method MUST be implemented in framework integrations. It is used to create an HttpRequest instance.
Parameters: | request – the “request” instance in framework |
---|---|
Returns: | HttpRequest instance |
create_oauth2_request
(request)¶This method MUST be implemented in framework integrations. It is used to create an OAuth2Request instance.
Parameters: | request – the “request” instance in framework |
---|---|
Returns: | OAuth2Request instance |
create_token_response
(request=None)¶Validate token request and create token response.
Parameters: | request – HTTP request instance |
---|
Find the authorization grant for current request.
Parameters: | request – OAuth2Request instance. |
---|---|
Returns: | grant instance |
get_error_uris
(request)¶Return a dict of error uris mapping. Framework SHOULD implement this function.
get_token_grant
(request)¶Find the token grant for current request.
Parameters: | request – OAuth2Request instance. |
---|---|
Returns: | grant instance |
get_translations
(request)¶Return a translations instance used for i18n error messages. Framework SHOULD implement this function.
handle_response
(status, body, headers)¶Return HTTP response. Framework MUST implement this function.
register_client_auth_method
(method, func)¶Add more client auth method. The default methods are:
Parameters: |
|
---|
The auth method accept two parameters: query_client
and request
,
an example for this method:
def authenticate_client_via_custom(query_client, request):
client_id = request.headers['X-Client-Id']
client = query_client(client_id)
do_some_validation(client)
return client
authorization_server.register_client_auth_method(
'custom', authenticate_client_via_custom)
register_endpoint
(endpoint_cls)¶Add extra endpoint to authorization server. e.g. RevocationEndpoint:
authorization_server.register_endpoint(RevocationEndpoint)
Parameters: | endpoint_cls – A endpoint class |
---|
register_grant
(grant_cls, extensions=None)¶Register a grant class into the endpoint registry. Developers
can implement the grants in authlib.oauth2.rfc6749.grants
and
register with this method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def authenticate_user(self, credential):
# ...
authorization_server.register_grant(AuthorizationCodeGrant)
Parameters: |
|
---|
send_signal
(name, *args, **kwargs)¶Framework integration can re-implement this method to support signal system.
validate_requested_scope
(scope, state=None)¶Validate if requested scope is supported by Authorization Server. Developers CAN re-write this method to meet your needs.
authlib.oauth2.rfc6749.
ResourceProtector
¶authlib.oauth2.rfc6749.
ClientMixin
¶Implementation of OAuth 2 Client described in Section 2 with some methods to help validation. A client has at least these information:
check_client_secret
(client_secret)¶Check client_secret matching with the client. For instance, in
the client table, the column is called client_secret
:
def check_client_secret(self, client_secret):
return self.client_secret == client_secret
Parameters: | client_secret – A string of client secret |
---|---|
Returns: | bool |
check_grant_type
(grant_type)¶Validate if the client can handle the given grant_type. There are four grant types defined by RFC6749:
For instance, there is a allowed_grant_types
column in your client:
def check_grant_type(self, grant_type):
return grant_type in self.grant_types
Parameters: | grant_type – the requested grant_type string. |
---|---|
Returns: | bool |
check_redirect_uri
(redirect_uri)¶Validate redirect_uri parameter in Authorization Endpoints. For
instance, in the client table, there is an allowed_redirect_uris
column:
def check_redirect_uri(self, redirect_uri):
return redirect_uri in self.allowed_redirect_uris
Parameters: | redirect_uri – A URL string for redirecting. |
---|---|
Returns: | bool |
check_response_type
(response_type)¶Validate if the client can handle the given response_type. There
are two response types defined by RFC6749: code and token. For
instance, there is a allowed_response_types
column in your client:
def check_response_type(self, response_type):
return response_type in self.response_types
Parameters: | response_type – the requested response_type string. |
---|---|
Returns: | bool |
check_token_endpoint_auth_method
(method)¶Check client token_endpoint_auth_method
defined via RFC7591.
Values defined by this specification are:
get_allowed_scope
(scope)¶A method to return a list of requested scopes which are supported by
this client. For instance, there is a scope
column:
def get_allowed_scope(self, scope):
if not scope:
return ''
allowed = set(scope_to_list(self.scope))
return list_to_scope([s for s in scope.split() if s in allowed])
Parameters: | scope – the requested scope. |
---|---|
Returns: | string of scope |
get_client_id
()¶A method to return client_id of the client. For instance, the value
in database is saved in a column called client_id
:
def get_client_id(self):
return self.client_id
Returns: | string |
---|
get_default_redirect_uri
()¶A method to get client default redirect_uri. For instance, the
database table for client has a column called default_redirect_uri
:
def get_default_redirect_uri(self):
return self.default_redirect_uri
Returns: | A URL string |
---|
has_client_secret
()¶A method returns that if the client has client_secret
value.
If the value is in client_secret
column:
def has_client_secret(self):
return bool(self.client_secret)
Returns: | bool |
---|
authlib.oauth2.rfc6749.
AuthorizationCodeMixin
¶get_redirect_uri
()¶A method to get authorization code’s redirect_uri
.
For instance, the database table for authorization code has a
column called redirect_uri
:
def get_redirect_uri(self):
return self.redirect_uri
Returns: | A URL string |
---|
get_scope
()¶A method to get scope of the authorization code. For instance,
the column is called scope
:
def get_scope(self):
return self.scope
Returns: | scope string |
---|
authlib.oauth2.rfc6749.
TokenMixin
¶get_client_id
()¶A method to return client_id of the token. For instance, the value
in database is saved in a column called client_id
:
def get_client_id(self):
return self.client_id
Returns: | string |
---|
get_expires_at
()¶A method to get the value when this token will be expired. e.g. it would be:
def get_expires_at(self):
return self.created_at + self.expires_in
Returns: | timestamp int |
---|
get_expires_in
()¶A method to get the expires_in
value of the token. e.g.
the column is called expires_in
:
def get_expires_in(self):
return self.expires_in
Returns: | timestamp int |
---|
get_scope
()¶A method to get scope of the authorization code. For instance,
the column is called scope
:
def get_scope(self):
return self.scope
Returns: | scope string |
---|
authlib.oauth2.rfc6749.
OAuth2Error
(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)¶get_body
()¶Get a list of body.
authlib.oauth2.rfc6749.
InsecureTransportError
(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)¶check
(uri)¶Check and raise InsecureTransportError with the given URI.
authlib.oauth2.rfc6749.
InvalidRequestError
(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)¶The request is missing a required parameter, includes an unsupported parameter value (other than grant type), repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed.
authlib.oauth2.rfc6749.
InvalidClientError
(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)¶Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported. If the client attempted to authenticate via the “Authorization” request header field, the authorization server MUST respond with an HTTP 401 (Unauthorized) status code and include the “WWW-Authenticate” response header field matching the authentication scheme used by the client.
authlib.oauth2.rfc6749.
InvalidGrantError
(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)¶The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
The authenticated client is not authorized to use this authorization grant type.
authlib.oauth2.rfc6749.
UnsupportedGrantTypeError
(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)¶The authorization grant type is not supported by the authorization server.
authlib.oauth2.rfc6749.
InvalidScopeError
(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)¶The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner.
authlib.oauth2.rfc6749.
AccessDeniedError
(description=None, uri=None, status_code=None, state=None, redirect_uri=None, redirect_fragment=False, error=None)¶The resource owner or authorization server denied the request.
Used in authorization endpoint for “code” and “implicit”. Defined in Section 4.1.2.1.
authlib.oauth2.rfc6749.grants.
AuthorizationCodeGrant
(request, server)¶The authorization code grant type is used to obtain both access tokens and refresh tokens and is optimized for confidential clients. Since this is a redirection-based flow, the client must be capable of interacting with the resource owner’s user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server:
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
TOKEN_ENDPOINT_AUTH_METHODS
= ['client_secret_basic', 'client_secret_post']¶Allowed client auth methods for token endpoint
AUTHORIZATION_CODE_LENGTH
= 48¶Generated “code” length
The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the “application/x-www-form-urlencoded” format. Per Section 4.1.1.
The client directs the resource owner to the constructed URI using an HTTP redirection response, or by other means available to it via the user-agent.
For example, the client directs the user-agent to make the following HTTP request using TLS (with extra line breaks for display purposes only):
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
The authorization server validates the request to ensure that all required parameters are present and valid. If the request is valid, the authorization server authenticates the resource owner and obtains an authorization decision (by asking the resource owner or by establishing approval via other means).
If the resource owner grants the access request, the authorization server issues an authorization code and delivers it to the client by adding the following parameters to the query component of the redirection URI using the “application/x-www-form-urlencoded” format. Per Section 4.1.2.
For example, the authorization server redirects the user-agent by sending the following HTTP response.
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
Parameters: |
|
---|---|
Returns: | (status_code, body, headers) |
validate_token_request
()¶The client makes a request to the token endpoint by sending the following parameters using the “application/x-www-form-urlencoded” format per Section 4.1.3:
If the client type is confidential or the client was issued client credentials (or assigned other authentication requirements), the client MUST authenticate with the authorization server as described in Section 3.2.1.
For example, the client makes the following HTTP request using TLS:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
create_token_response
()¶If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token as described in Section 5.1. If the request client authentication failed or is invalid, the authorization server returns an error response as described in Section 5.2. Per Section 4.1.4.
An example successful response:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
Returns: | (status_code, body, headers) |
---|
“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
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=request.user.id,
)
item.save()
Get authorization_code from previously savings. Developers MUST implement it in subclass:
def query_authorization_code(self, code, client):
return Authorization.get(code=code, client_id=client.client_id)
Parameters: |
|
---|---|
Returns: | authorization_code object |
Delete authorization code from database or cache. Developers MUST implement it in subclass, e.g.:
def delete_authorization_code(self, authorization_code):
authorization_code.delete()
Parameters: | authorization_code – the instance of authorization_code |
---|
authenticate_user
(authorization_code)¶Authenticate the user related to this authorization_code. Developers MUST implement this method in subclass, e.g.:
def authenticate_user(self, authorization_code):
return User.query.get(authorization_code.user_id)
Parameters: | authorization_code – AuthorizationCode object |
---|---|
Returns: | user |
authlib.oauth2.rfc6749.grants.
ImplicitGrant
(request, server)¶The implicit grant type is used to obtain access tokens (it does not support the issuance of refresh tokens) and is optimized for public clients known to operate a particular redirection URI. These clients are typically implemented in a browser using a scripting language such as JavaScript.
Since this is a redirection-based flow, the client must be capable of interacting with the resource owner’s user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server.
Unlike the authorization code grant type, in which the client makes separate requests for authorization and for an access token, the client receives the access token as the result of the authorization request.
The implicit grant type does not include client authentication, and relies on the presence of the resource owner and the registration of the redirection URI. Because the access token is encoded into the redirection URI, it may be exposed to the resource owner and other applications residing on the same device:
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+
AUTHORIZATION_ENDPOINT
= True¶authorization_code grant type has authorization endpoint
TOKEN_ENDPOINT_AUTH_METHODS
= ['none']¶Allowed client auth methods for token endpoint
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.
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
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.
The authorization server MUST NOT issue a refresh token.
For example, the authorization server redirects the user-agent by sending the following HTTP response:
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
&state=xyz&token_type=example&expires_in=3600
Developers should note that some user-agents do not support the inclusion of a fragment component in the HTTP “Location” response header field. Such clients will require using other methods for redirecting the client than a 3xx redirection response – for example, returning an HTML page that includes a ‘continue’ button with an action linked to the redirection URI.
Parameters: |
|
---|---|
Returns: | (status_code, body, headers) |
authlib.oauth2.rfc6749.grants.
ResourceOwnerPasswordCredentialsGrant
(request, server)¶The resource owner password credentials grant type is suitable in cases where the resource owner has a trust relationship with the client, such as the device operating system or a highly privileged
application. The authorization server should take special care when enabling this grant type and only allow it when other flows are not viable.
This grant type is suitable for clients capable of obtaining the resource owner’s credentials (username and password, typically using an interactive form). It is also used to migrate existing clients using direct authentication schemes such as HTTP Basic or Digest authentication to OAuth by converting the stored credentials to an access token:
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
validate_token_request
()¶The client makes a request to the token endpoint by adding the following parameters using the “application/x-www-form-urlencoded” format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body:
If the client type is confidential or the client was issued client credentials (or assigned other authentication requirements), the client MUST authenticate with the authorization server as described in Section 3.2.1.
For example, the client makes the following HTTP request using transport-layer security (with extra line breaks for display purposes only):
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
create_token_response
()¶If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token as described in Section 5.1. If the request failed client authentication or is invalid, the authorization server returns an error response as described in Section 5.2.
An example successful response:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
Returns: | (status_code, body, headers) |
---|
authenticate_user
(username, password)¶validate the resource owner password credentials using its existing password validation algorithm:
def authenticate_user(self, username, password):
user = get_user_by_username(username)
if user.check_password(password):
return user
authlib.oauth2.rfc6749.grants.
ClientCredentialsGrant
(request, server)¶The client can request an access token using only its client credentials (or other supported means of authentication) when the client is requesting access to the protected resources under its control, or those of another resource owner that have been previously arranged with the authorization server.
The client credentials grant type MUST only be used by confidential clients:
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
https://tools.ietf.org/html/rfc6749#section-4.4
validate_token_request
()¶The client makes a request to the token endpoint by adding the following parameters using the “application/x-www-form-urlencoded” format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body:
The client MUST authenticate with the authorization server as described in Section 3.2.1.
For example, the client makes the following HTTP request using transport-layer security (with extra line breaks for display purposes only):
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
The authorization server MUST authenticate the client.
create_token_response
()¶If the access token request is valid and authorized, the authorization server issues an access token as described in Section 5.1. A refresh token SHOULD NOT be included. If the request failed client authentication or is invalid, the authorization server returns an error response as described in Section 5.2.
An example successful response:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"example_parameter":"example_value"
}
Returns: | (status_code, body, headers) |
---|
authlib.oauth2.rfc6749.grants.
RefreshTokenGrant
(request, server)¶A special grant endpoint for refresh_token grant_type. Refreshing an Access Token per Section 6.
INCLUDE_NEW_REFRESH_TOKEN
= False¶The authorization server MAY issue a new refresh token
validate_token_request
()¶If the authorization server issued a refresh token to the client, the client makes a refresh request to the token endpoint by adding the following parameters using the “application/x-www-form-urlencoded” format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body, per Section 6:
For example, the client makes the following HTTP request using transport-layer security (with extra line breaks for display purposes only):
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
create_token_response
()¶If valid and authorized, the authorization server issues an access token as described in Section 5.1. If the request failed verification or is invalid, the authorization server returns an error response as described in Section 5.2.
authenticate_refresh_token
(refresh_token)¶Get token information with refresh_token string. Developers MUST implement this method in subclass:
def authenticate_refresh_token(self, refresh_token):
item = Token.get(refresh_token=refresh_token)
if item and item.is_refresh_token_active():
return item
Parameters: | refresh_token – The refresh token issued to the client |
---|---|
Returns: | token |
authenticate_user
(credential)¶Authenticate the user related to this credential. Developers MUST implement this method in subclass:
def authenticate_user(self, credential):
return User.query.get(credential.user_id)
Parameters: | credential – Token object |
---|---|
Returns: | user |
revoke_old_credential
(credential)¶The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client. Developers MUST implement this method in subclass:
def revoke_old_credential(self, credential):
credential.revoked = True
credential.save()
Parameters: | credential – Token object |
---|
This section contains the generic implementation of RFC6750.
Bearer token is used in OAuth 2.0 framework to protect resources. You need
to implement the missing methods of BearerTokenValidator
before
using it. Learn how to use it in Resource Server.
authlib.oauth2.rfc6750.
BearerTokenValidator
(realm=None)¶authenticate_token
(token_string)¶A method to query token from database with the given token string. Developers MUST re-implement this method. For instance:
def authenticate_token(self, token_string):
return get_token_from_database(token_string)
Parameters: | token_string – A string to represent the access_token. |
---|---|
Returns: | token |
request_invalid
(request)¶Check if the HTTP request is valid or not. Developers MUST re-implement this method. For instance, your server requires a “X-Device-Version” in the header:
def request_invalid(self, request):
return 'X-Device-Version' in request.headers
Usually, you don’t have to detect if the request is valid or not,
you can just return a False
.
Parameters: | request – instance of HttpRequest |
---|---|
Returns: | Boolean |
token_revoked
(token)¶Check if this token is revoked. Developers MUST re-implement this
method. If there is a column called revoked
on the token table:
def token_revoked(self, token):
return token.revoked
Parameters: | token – token instance |
---|---|
Returns: | Boolean |
authlib.oauth2.rfc6750.
BearerToken
(access_token_generator, refresh_token_generator=None, expires_generator=None)¶Bearer Token generator which can create the payload for token response by OAuth 2 server. A typical token response would be:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"mF_9.B5f-4.1JqM",
"token_type":"Bearer",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}
Parameters: |
|
---|---|
Returns: | Callable |
When BearerToken is initialized, it will be callable:
token_generator = BearerToken(access_token_generator)
token = token_generator(client, grant_type, expires_in=None,
scope=None, include_refresh_token=True)
The callable function that BearerToken created accepts these parameters:
Parameters: |
|
---|---|
Returns: | Token dict |
DEFAULT_EXPIRES_IN
= 3600¶default expires_in value
GRANT_TYPES_EXPIRES_IN
= {'authorization_code': 864000, 'client_credentials': 864000, 'implicit': 3600, 'password': 864000}¶default expires_in value differentiate by grant_type
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)
authlib.oauth2.rfc7009.
RevocationEndpoint
(server)¶Implementation of revocation endpoint which is described in RFC7009.
ENDPOINT_NAME
= 'revocation'¶Endpoint name to be registered
authenticate_endpoint_credential
(request, client)¶The client constructs the request by including the following parameters using the “application/x-www-form-urlencoded” format in the HTTP request entity-body:
create_endpoint_response
(request)¶Validate revocation request and create the response for revocation. For example, a client may request the revocation of a refresh token with the following request:
POST /revoke HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
Returns: | (status_code, body, headers) |
---|
query_token
(token, token_type_hint, client)¶Get the token from database/storage by the given token string. Developers should implement this method:
def query_token(self, token, token_type_hint, client):
if token_type_hint == 'access_token':
return Token.query_by_access_token(token, client.client_id)
if token_type_hint == 'refresh_token':
return Token.query_by_refresh_token(token, client.client_id)
return Token.query_by_access_token(token, client.client_id) or Token.query_by_refresh_token(token, client.client_id)
revoke_token
(token)¶Mark token as revoked. Since token MUST be unique, it would be dangerous to delete it. Consider this situation:
It would be secure to mark a token as revoked:
def revoke_token(self, token):
token.revoked = True
token.save()
authenticate_endpoint_client
(request)¶Authentication client for endpoint with CLIENT_AUTH_METHODS
.
This section contains the generic implementation of RFC7515. Find how to use it in JWS Guide.
authlib.jose.
JsonWebSignature
(algorithms=None, private_headers=None)¶REGISTERED_HEADER_PARAMETER_NAMES
= frozenset({'jku', 'crit', 'x5u', 'alg', 'x5t#S256', 'typ', 'x5t', 'kid', 'x5c', 'cty', 'jwk'})¶Registered Header Parameter Names defined by Section 4.1
ALGORITHMS_REGISTRY
= {'ES256': <authlib.jose.rfc7518._cryptography_backends._jws.ECAlgorithm object>, 'ES384': <authlib.jose.rfc7518._cryptography_backends._jws.ECAlgorithm object>, 'ES512': <authlib.jose.rfc7518._cryptography_backends._jws.ECAlgorithm object>, 'EdDSA': <authlib.jose.rfc8037._jws_cryptography.EdDSAAlgorithm object>, 'HS256': <authlib.jose.rfc7518.jws_algorithms.HMACAlgorithm object>, 'HS384': <authlib.jose.rfc7518.jws_algorithms.HMACAlgorithm object>, 'HS512': <authlib.jose.rfc7518.jws_algorithms.HMACAlgorithm object>, 'PS256': <authlib.jose.rfc7518._cryptography_backends._jws.RSAPSSAlgorithm object>, 'PS384': <authlib.jose.rfc7518._cryptography_backends._jws.RSAPSSAlgorithm object>, 'PS512': <authlib.jose.rfc7518._cryptography_backends._jws.RSAPSSAlgorithm object>, 'RS256': <authlib.jose.rfc7518._cryptography_backends._jws.RSAAlgorithm object>, 'RS384': <authlib.jose.rfc7518._cryptography_backends._jws.RSAAlgorithm object>, 'RS512': <authlib.jose.rfc7518._cryptography_backends._jws.RSAAlgorithm object>, 'none': <authlib.jose.rfc7518.jws_algorithms.NoneAlgorithm object>}¶Defined available JWS algorithms in the registry
serialize_compact
(protected, payload, key)¶Generate a JWS Compact Serialization. The JWS Compact Serialization represents digitally signed or MACed content as a compact, URL-safe string, per Section 7.1.
BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)
Parameters: |
|
---|---|
Returns: | byte |
deserialize_compact
(s, key, decode=None)¶Exact JWS Compact Serialization, and validate with the given key. If key is not provided, the returned dict will contain the signature, and signing input values. Via Section 7.1.
Parameters: |
|
---|---|
Returns: | JWSObject |
Raise: | BadSignatureError |
serialize_json
(header_obj, payload, key)¶Generate a JWS JSON Serialization. The JWS JSON Serialization represents digitally signed or MACed content as a JSON object, per Section 7.2.
Parameters: |
|
---|---|
Returns: | JWSObject |
Example header_obj
of JWS JSON Serialization:
{
"protected: {"alg": "HS256"},
"header": {"kid": "jose"}
}
Pass a dict to generate flattened JSON Serialization, pass a list of header dict to generate standard JSON Serialization.
deserialize_json
(obj, key, decode=None)¶Exact JWS JSON Serialization, and validate with the given key. If key is not provided, it will return a dict without signature verification. Header will still be validated. Via Section 7.2.
Parameters: |
|
---|---|
Returns: | JWSObject |
Raise: | BadSignatureError |
serialize
(header, payload, key)¶Generate a JWS Serialization. It will automatically generate a
Compact or JSON Serialization depending on the given header. If a
header is in a JSON header format, it will call
serialize_json()
, otherwise it will call
serialize_compact()
.
Parameters: |
|
---|---|
Returns: | byte/dict |
deserialize
(s, key, decode=None)¶Deserialize JWS Serialization, both compact and JSON format. It will automatically deserialize depending on the given JWS.
Parameters: |
|
---|---|
Returns: | dict |
Raise: | BadSignatureError |
If key is not provided, it will still deserialize the serialization without verification.
authlib.jose.
JWSHeader
(protected, header)¶Header object for JWS. It combine the protected header and unprotected header together. JWSHeader itself is a dict of the combined dict. e.g.
>>> protected = {'alg': 'HS256'}
>>> header = {'kid': 'a'}
>>> jws_header = JWSHeader(protected, header)
>>> print(jws_header)
{'alg': 'HS256', 'kid': 'a'}
>>> jws_header.protected == protected
>>> jws_header.header == header
Parameters: |
|
---|
authlib.jose.
JWSObject
(header, payload, type='compact')¶A dict instance to represent a JWS object.
authlib.jose.
JWSAlgorithm
¶Interface for JWS algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWS with this base implementation.
prepare_key
(raw_data)¶Prepare key for signing and verifying signature.
sign
(msg, key)¶Sign the text msg with a private/sign key.
Parameters: |
|
---|---|
Returns: | bytes |
verify
(msg, sig, key)¶Verify the signature of text msg with a public/verify key.
Parameters: |
|
---|---|
Returns: | boolean |
This section contains the generic implementation of RFC7516. Find how to use it in JWE Guide.
authlib.jose.
JsonWebEncryption
(algorithms=None, private_headers=None)¶REGISTERED_HEADER_PARAMETER_NAMES
= frozenset({'jku', 'crit', 'alg', 'x5u', 'zip', 'x5t#S256', 'typ', 'enc', 'x5t', 'kid', 'x5c', 'cty', 'jwk'})¶Registered Header Parameter Names defined by Section 4.1
register_algorithm
(algorithm)¶Register an algorithm for alg
or enc
or zip
of JWE.
serialize_compact
(protected, payload, key)¶Generate a JWE Compact Serialization. The JWE Compact Serialization represents encrypted content as a compact, URL-safe string. This string is:
BASE64URL(UTF8(JWE Protected Header)) || ‘.’ || BASE64URL(JWE Encrypted Key) || ‘.’ || BASE64URL(JWE Initialization Vector) || ‘.’ || BASE64URL(JWE Ciphertext) || ‘.’ || BASE64URL(JWE Authentication Tag)
Only one recipient is supported by the JWE Compact Serialization and it provides no syntax to represent JWE Shared Unprotected Header, JWE Per-Recipient Unprotected Header, or JWE AAD values.
Parameters: |
|
---|---|
Returns: | byte |
deserialize_compact
(s, key, decode=None)¶Exact JWS Compact Serialization, and validate with the given key.
Parameters: |
|
---|---|
Returns: | dict |
authlib.jose.
JWEAlgorithm
¶Interface for JWE algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWE with this base implementation.
authlib.jose.
JWEEncAlgorithm
¶encrypt
(msg, aad, iv, key)¶Encrypt the given “msg” text.
Parameters: |
|
---|---|
Returns: | (ciphertext, iv, tag) |
decrypt
(ciphertext, aad, iv, tag, key)¶Decrypt the given cipher text.
Parameters: |
|
---|---|
Returns: | message |
authlib.jose.
JWEZipAlgorithm
¶This section contains the generic implementation of RFC7517. Find how to use it in JWK Guide.
authlib.jose.
JsonWebKey
¶generate_key
(kty, crv_or_size, options=None, is_private=False)¶Generate a Key with the given key type, curve name or bit size.
Parameters: |
|
---|---|
Returns: | Key instance |
import_key
(raw, options=None)¶Import a Key from bytes, string, PEM or dict.
Returns: | Key instance |
---|
import_key_set
(raw)¶Import KeySet from string, dict or a list of keys.
Returns: | KeySet instance |
---|
authlib.jose.
Key
(payload)¶This is the base class for a JSON Web Key.
RAW_KEY_CLS
¶alias of builtins.bytes
get_op_key
(operation)¶Get the raw key for the given key_op. This method will also check if the given key_op is supported by this key.
Parameters: | operation – key operation value, such as “sign”, “encrypt”. |
---|---|
Returns: | raw key |
check_key_op
(operation)¶Check if the given key_op is supported by this key.
Parameters: | operation – key operation value, such as “sign”, “encrypt”. |
---|---|
Raise: | ValueError |
as_key
()¶Represent this key as raw key.
as_dict
(add_kid=False)¶Represent this key as a dict of the JSON Web Key.
as_json
()¶Represent this key as a JSON string.
as_pem
()¶Represent this key as string in PEM format.
thumbprint
()¶Implementation of RFC7638 JSON Web Key (JWK) Thumbprint.
authlib.jose.
KeySet
(keys)¶This class represents a JSON Web Key Set.
as_dict
()¶Represent this key as a dict of the JSON Web Key Set.
as_json
()¶Represent this key set as a JSON string.
find_by_kid
(kid)¶Find the key matches the given kid value.
Parameters: | kid – A string of kid |
---|---|
Returns: | Key instance |
Raise: | ValueError |
authlib.jose.
OctKey
(payload)¶Key class of the oct
key type.
get_op_key
(key_op)¶Get the raw key for the given key_op. This method will also check if the given key_op is supported by this key.
Parameters: | operation – key operation value, such as “sign”, “encrypt”. |
---|---|
Returns: | raw key |
import_key
(raw, options=None)¶Import a key from bytes, string, or dict data.
generate_key
(key_size=256, options=None, is_private=False)¶Generate a OctKey
with the given bit size.
authlib.jose.
RSAKey
(payload)¶Key class of the RSA
key type.
as_pem
(is_private=False, password=None)¶Export key into PEM format bytes.
Parameters: |
|
---|---|
Returns: | bytes |
import_key
(raw, options=None)¶Import a key from PEM or dict data.
authlib.jose.
ECKey
(payload)¶Key class of the EC
key type.
as_pem
(is_private=False, password=None)¶Export key into PEM format bytes.
Parameters: |
|
---|---|
Returns: | bytes |
import_key
(raw, options=None)¶Import a key from PEM or dict data.
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.
Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.3.
Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.4.
Algorithms in this section requires extra crypto backends. This section is defined by RFC7518 Section 3.5.
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
:
Current available algorithms for enc
:
Current available algorithms for zip
:
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.
authlib.jose.
JsonWebToken
(algorithms=None, private_headers=None)¶check_sensitive_data
(payload)¶Check if payload contains sensitive information.
encode
(header, payload, key, check=True)¶Encode a JWT with the given header, payload and key.
Parameters: |
|
---|---|
Returns: | bytes |
decode
(s, key, claims_cls=None, claims_options=None, claims_params=None)¶Decode the JWS with the given key. This is similar with
verify()
, except that it will raise BadSignatureError when
signature doesn’t match.
Parameters: |
|
---|---|
Returns: | claims_cls instance |
Raise: | BadSignatureError |
authlib.jose.
JWTClaims
(payload, header, options=None, params=None)¶validate
(now=None, leeway=0)¶Validate everything in claims payload.
validate_iss
()¶The “iss” (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The “iss” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
validate_sub
()¶The “sub” (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The “sub” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
validate_aud
()¶The “aud” (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the “aud” claim when this claim is present, then the JWT MUST be rejected. In the general case, the “aud” value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the “aud” value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
validate_exp
(now, leeway)¶The “exp” (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the “exp” claim requires that the current date/time MUST be before the expiration date/time listed in the “exp” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
validate_nbf
(now, leeway)¶The “nbf” (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the “nbf” claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the “nbf” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
validate_iat
(now, leeway)¶The “iat” (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
validate_jti
()¶The “jti” (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The “jti” claim can be used to prevent the JWT from being replayed. The “jti” value is a case- sensitive string. Use of this claim is OPTIONAL.
This section contains the generic Python implementation of RFC7523.
JWT Profile for OAuth 2.0 Authorization Grants works in the same way with
RFC6749 built-in grants. Which means it can be
registered with register_grant()
.
The base class is JWTBearerGrant
, you need to implement the missing
methods in order to use it. Here is an example:
from authlib.jose import jwk
from authlib.oauth2.rfc7523 import JWTBearerGrant as _JWTBearerGrant
class JWTBearerGrant(_JWTBearerGrant):
def authenticate_user(self, client, claims):
# get user from claims info, usually it is claims['sub']
# for anonymous user, return None
return None
def authenticate_client(self, claims):
# get client from claims, usually it is claims['iss']
# since the assertion JWT is generated by this client
return get_client_by_iss(claims['iss'])
def resolve_public_key(self, headers, payload):
# get public key to decode the assertion JWT
jwk_set = get_client_public_keys(claims['iss'])
return jwk.loads(jwk_set, header.get('kid'))
# register grant to authorization server
authorization_server.register_grant(JWTBearerGrant)
When creating a client, authorization server will generate several key pairs. The server itself can only keep the public keys, which will be used to decode assertion value.
For client implementation, check out:
AssertionSession
.AssertionSession
.AsyncAssertionSession
.In RFC6749: The OAuth 2.0 Authorization Framework, Authlib provided three built-in client authentication
methods, which are none
, client_secret_post
and client_secret_basic
.
With the power of Assertion Framework, we can add more client authentication
methods. In this section, Authlib provides two more options:
client_secret_jwt
and private_key_jwt
. RFC7523 itself doesn’t define
any names, these two names are defined by OpenID Connect in ClientAuthentication.
The AuthorizationServer
has provided a method
register_client_auth_method()
to add more client authentication methods.
In Authlib, client_secret_jwt
and private_key_jwt
share the same API,
using JWTBearerClientAssertion
to create a new client authentication:
class JWTClientAuth(JWTBearerClientAssertion):
def validate_jti(self, claims, jti):
# validate_jti is required by OpenID Connect
# but it is optional by RFC7523
# use cache to validate jti value
key = 'jti:{}-{}'.format(claims['sub'], jti)
if cache.get(key):
return False
cache.set(key, 1, timeout=3600)
return True
def resolve_client_public_key(self, client, headers):
if headers['alg'] == 'HS256':
return client.client_secret
if headers['alg'] == 'RS256':
return client.public_key
# you may support other ``alg`` value
authorization_server.register_client_auth_method(
JWTClientAuth.CLIENT_AUTH_METHOD,
JWTClientAuth('https://example.com/oauth/token')
)
The value https://example.com/oauth/token
is your authorization servers’s
token endpoint, which is used as aud
value in JWT.
Now we have added this client auth method to authorization server, but no grant types support this authentication method, you need to add it to the supported grant types too, e.g. we want to support this in authorization code grant:
from authlib.oauth2.rfc6749 import grants
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic',
JWTClientAuth.CLIENT_AUTH_METHOD,
]
# ...
You may noticed that the value of CLIENT_AUTH_METHOD
is
client_assertion_jwt
. It is not client_secret_jwt
or
private_key_jwt
, because they have the same logic. In the above
implementation:
def resolve_client_public_key(self, client, headers):
alg = headers['alg']
If this alg
is a MAC SHA like HS256
, it is called client_secret_jwt
,
because the key used to sign a JWT is the client’s client_secret
value. If
this alg
is RS256
or something else, it is called private_key_jwt
,
because client will use its private key to sign the JWT. You can set a limitation
in the implementation of resolve_client_public_key
to accept only HS256
alg, in this case, you can also alter CLIENT_AUTH_METHOD = 'client_secret_jwt'
.
Authlib RFC7523 provides two more client authentication methods for OAuth 2 Session:
client_secret_jwt
private_key_jwt
Here is an example of how to register client_secret_jwt
for OAuth2Session
:
from authlib.oauth2.rfc7523 import ClientSecretJWT
from authlib.integrations.requests_client import OAuth2Session
session = OAuth2Session(
'your-client-id', 'your-client-secret',
token_endpoint_auth_method='client_secret_jwt'
)
token_endpoint = 'https://example.com/oauth/token'
session.register_client_auth_method(ClientSecretJWT(token_endpoint))
session.fetch_token(token_endpoint)
How about private_key_jwt
? It is the same as client_secret_jwt
:
from authlib.oauth2.rfc7523 import PrivateKeyJWT
with open('your-private-key.pem', 'rb') as f:
private_key = f.read()
session = OAuth2Session(
'your-client-id', private_key,
token_endpoint_auth_method='private_key_jwt' # NOTICE HERE
)
token_endpoint = 'https://example.com/oauth/token'
session.register_client_auth_method(PrivateKeyJWT(token_endpoint))
session.fetch_token(token_endpoint)
authlib.oauth2.rfc7523.
JWTBearerGrant
(request, server)¶create_claims_options
()¶Create a claims_options for verify JWT payload claims. Developers MAY overwrite this method to create a more strict options.
process_assertion_claims
(assertion)¶Extract JWT payload claims from request “assertion”, per Section 3.1.
Parameters: | assertion – assertion string value in the request |
---|---|
Returns: | JWTClaims |
Raise: | InvalidGrantError |
validate_token_request
()¶The client makes a request to the token endpoint by sending the following parameters using the “application/x-www-form-urlencoded” format per Section 2.1:
The following example demonstrates an access token request with a JWT as an authorization grant:
POST /token.oauth2 HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.
eyJpc3Mi[...omitted for brevity...].
J9l-ZhwP[...omitted for brevity...]
create_token_response
()¶If valid and authorized, the authorization server issues an access token.
authenticate_user
(client, claims)¶Authenticate user with the given assertion claims. Developers MUST implement it in subclass, e.g.:
def authenticate_user(self, client, claims):
user = User.get_by_sub(claims['sub'])
if is_authorized_to_client(user, client):
return user
Parameters: |
|
---|---|
Returns: | User instance |
authenticate_client
(claims)¶Authenticate client with the given assertion claims. Developers MUST implement it in subclass, e.g.:
def authenticate_client(self, claims):
return Client.get_by_iss(claims['iss'])
Parameters: | claims – assertion payload claims |
---|---|
Returns: | Client instance |
resolve_public_key
(headers, payload)¶Find public key to verify assertion signature. Developers MUST implement it in subclass, e.g.:
def resolve_public_key(self, headers, payload):
jwk_set = get_jwk_set_by_iss(payload['iss'])
return filter_jwk_set(jwk_set, headers['kid'])
Parameters: |
|
---|---|
Returns: | A public key |
authlib.oauth2.rfc7523.
JWTBearerClientAssertion
(token_url, validate_jti=True)¶Implementation of Using JWTs for Client Authentication, which is defined by RFC7523.
CLIENT_ASSERTION_TYPE
= 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'¶Value of client_assertion_type
of JWTs
CLIENT_AUTH_METHOD
= 'client_assertion_jwt'¶Name of the client authentication method
create_claims_options
()¶Create a claims_options for verify JWT payload claims. Developers MAY overwrite this method to create a more strict options.
process_assertion_claims
(assertion, resolve_key)¶Extract JWT payload claims from request “assertion”, per Section 3.1.
Parameters: |
|
---|---|
Returns: | JWTClaims |
Raise: | InvalidClientError |
validate_jti
(claims, jti)¶Validate if the given jti
value is used before. Developers
MUST implement this method:
def validate_jti(self, claims, jti):
key = 'jti:{}-{}'.format(claims['sub'], jti)
if redis.get(key):
return False
redis.set(key, 1, ex=3600)
return True
resolve_client_public_key
(client, headers)¶Resolve the client public key for verifying the JWT signature.
A client may have many public keys, in this case, we can retrieve it
via kid
value in headers. Developers MUST implement this method:
def resolve_client_public_key(self, client, headers):
return client.public_key
authlib.oauth2.rfc7523.
ClientSecretJWT
(token_endpoint=None, claims=None)¶Authentication method for OAuth 2.0 Client. This authentication
method is called client_secret_jwt
, which is using client_id
and client_secret
constructed with JWT to identify a client.
Here is an example of use client_secret_jwt
with Requests Session:
from authlib.integrations.requests_client import OAuth2Session
token_endpoint = 'https://example.com/oauth/token'
session = OAuth2Session(
'your-client-id', 'your-client-secret',
token_endpoint_auth_method='client_secret_jwt'
)
session.register_client_auth_method(ClientSecretJWT(token_endpoint))
session.fetch_token(token_endpoint)
Parameters: |
|
---|
authlib.oauth2.rfc7523.
PrivateKeyJWT
(token_endpoint=None, claims=None)¶Authentication method for OAuth 2.0 Client. This authentication
method is called private_key_jwt
, which is using client_id
and private_key
constructed with JWT to identify a client.
Here is an example of use private_key_jwt
with Requests Session:
from authlib.integrations.requests_client import OAuth2Session
token_endpoint = 'https://example.com/oauth/token'
session = OAuth2Session(
'your-client-id', 'your-client-private-key',
token_endpoint_auth_method='private_key_jwt'
)
session.register_client_auth_method(PrivateKeyJWT(token_endpoint))
session.fetch_token(token_endpoint)
Parameters: |
|
---|
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)
authlib.oauth2.rfc7591.
ClientRegistrationEndpoint
(server)¶The client registration endpoint is an OAuth 2.0 endpoint designed to allow a client to be registered with the authorization server.
claims_class
¶alias of authlib.oauth2.rfc7591.claims.ClientMetadataClaims
software_statement_alg_values_supported
= None¶Rewrite this value with a list to support software_statement
e.g. software_statement_alg_values_supported = ['RS256']
get_claims_options
()¶Generate claims options validation from Authorization Server metadata.
generate_client_registration_info
(client, request)¶Generate `registration_client_uri
and registration_access_token
for RFC7592. This method returns None
by default. Developers MAY rewrite
this method to return registration information.
generate_client_id
()¶Generate client_id
value. Developers MAY rewrite this method
to use their own way to generate client_id
.
generate_client_secret
()¶Generate client_secret
value. Developers MAY rewrite this method
to use their own way to generate client_secret
.
authenticate_token
(request)¶Authenticate current credential who is requesting to register a client. Developers MUST implement this method in subclass:
def authenticate_token(self, request):
auth = request.headers.get('Authorization')
return get_token_by_auth(auth)
Returns: | token instance |
---|
resolve_public_key
(request)¶Resolve a public key for decoding software_statement
. If
enable_software_statement=True
, developers MUST implement this
method in subclass:
def resolve_public_key(self, request):
return get_public_key_from_user(request.credential)
Returns: | JWK or Key string |
---|
save_client
(client_info, client_metadata, request)¶Save client into database. Developers MUST implement this method in subclass:
def save_client(self, client_info, client_metadata, request):
client = OAuthClient(
client_id=client_info['client_id'],
client_secret=client_info['client_secret'],
...
)
client.save()
return client
authlib.oauth2.rfc7591.
ClientMetadataClaims
(payload, header, options=None, params=None)¶validate_redirect_uris
()¶Array of redirection URI strings for use in redirect-based flows such as the authorization code and implicit flows. As required by Section 2 of OAuth 2.0 [RFC6749], clients using flows with redirection MUST register their redirection URI values. Authorization servers that support dynamic registration for redirect-based flows MUST implement support for this metadata value.
validate_token_endpoint_auth_method
()¶String indicator of the requested authentication method for the token endpoint.
validate_grant_types
()¶Array of OAuth 2.0 grant type strings that the client can use at the token endpoint.
validate_response_types
()¶Array of the OAuth 2.0 response type strings that the client can use at the authorization endpoint.
validate_client_name
()¶Human-readable string name of the client to be presented to the end-user during authorization. If omitted, the authorization server MAY display the raw “client_id” value to the end-user instead. It is RECOMMENDED that clients always send this field. The value of this field MAY be internationalized, as described in Section 2.2.
validate_client_uri
()¶URL string of a web page providing information about the client. If present, the server SHOULD display this URL to the end-user in a clickable fashion. It is RECOMMENDED that clients always send this field. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.
validate_logo_uri
()¶URL string that references a logo for the client. If present, the server SHOULD display this image to the end-user during approval. The value of this field MUST point to a valid image file. The value of this field MAY be internationalized, as described in Section 2.2.
validate_scope
()¶String containing a space-separated list of scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) that the client can use when requesting access tokens. The semantics of values in this list are service specific. If omitted, an authorization server MAY register a client with a default set of scopes.
validate_contacts
()¶Array of strings representing ways to contact people responsible for this client, typically email addresses. The authorization server MAY make these contact addresses available to end-users for support requests for the client. See Section 6 for information on Privacy Considerations.
validate_tos_uri
()¶URL string that points to a human-readable terms of service document for the client that describes a contractual relationship between the end-user and the client that the end-user accepts when authorizing the client. The authorization server SHOULD display this URL to the end-user if it is provided. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.
validate_policy_uri
()¶URL string that points to a human-readable privacy policy document that describes how the deployment organization collects, uses, retains, and discloses personal data. The authorization server SHOULD display this URL to the end-user if it is provided. The value of this field MUST point to a valid web page. The value of this field MAY be internationalized, as described in Section 2.2.
validate_jwks_uri
()¶URL string referencing the client’s JSON Web Key (JWK) Set [RFC7517] document, which contains the client’s public keys. The value of this field MUST point to a valid JWK Set document. These keys can be used by higher-level protocols that use signing or encryption. For instance, these keys might be used by some applications for validating signed requests made to the token endpoint when using JWTs for client authentication [RFC7523]. Use of this parameter is preferred over the “jwks” parameter, as it allows for easier key rotation. The “jwks_uri” and “jwks” parameters MUST NOT both be present in the same request or response.
validate_jwks
()¶Client’s JSON Web Key Set [RFC7517] document value, which contains the client’s public keys. The value of this field MUST be a JSON object containing a valid JWK Set. These keys can be used by higher-level protocols that use signing or encryption. This parameter is intended to be used by clients that cannot use the “jwks_uri” parameter, such as native clients that cannot host public URLs. The “jwks_uri” and “jwks” parameters MUST NOT both be present in the same request or response.
validate_software_id
()¶A unique identifier string (e.g., a Universally Unique Identifier (UUID)) assigned by the client developer or software publisher used by registration endpoints to identify the client software to be dynamically registered. Unlike “client_id”, which is issued by the authorization server and SHOULD vary between instances, the “software_id” SHOULD remain the same for all instances of the client software. The “software_id” SHOULD remain the same across multiple updates or versions of the same piece of software. The value of this field is not intended to be human readable and is usually opaque to the client and authorization server.
validate_software_version
()¶A version identifier string for the client software identified by “software_id”. The value of the “software_version” SHOULD change on any update to the client software identified by the same “software_id”. The value of this field is intended to be compared using string equality matching and no other comparison semantics are defined by this specification. The value of this field is outside the scope of this specification, but it is not intended to be human readable and is usually opaque to the client and authorization server. The definition of what constitutes an update to client software that would trigger a change to this value is specific to the software itself and is outside the scope of this specification.
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.
In order to apply proof key for code exchange, you need to register the
CodeChallenge
extension to AuthorizationCodeGrant
. But before
that, we need to re-design our AuthorizationCode database.
The new database SHOULD contain two more columns:
And the AuthorizationCodeGrant
should record the code_challenge
and
code_challenge_method
into database in save_authorization_code
method:
class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
# YOU MAY NEED TO ADD "none" METHOD FOR AUTHORIZATION WITHOUT CLIENT SECRET
TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post', 'none']
def save_authorization_code(self, code, request):
# NOTICE BELOW
code_challenge = request.data.get('code_challenge')
code_challenge_method = request.data.get('code_challenge_method')
auth_code = AuthorizationCode(
code=code,
client_id=request.client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=request.user.id,
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
)
auth_code.save()
return auth_code
Now you can register your AuthorizationCodeGrant
with the extension:
from authlib.oauth2.rfc7636 import CodeChallenge
server.register_grant(MyAuthorizationCodeGrant, [CodeChallenge(required=True)])
If required=True
, code challenge is required for authorization code flow.
If required=False
, it is optional, it will only valid the code challenge
when clients send these parameters.
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)
The authorization flow is the same as in OAuth 2 Session, what you need to do is:
code_challenge
and code_challenge_method
in .create_authorization_url
.code_verifier
in .fetch_token
.authlib.oauth2.rfc7636.
CodeChallenge
(required=True)¶CodeChallenge extension to Authorization Code Grant. It is used to improve the security of Authorization Code flow for public clients by sending extra “code_challenge” and “code_verifier” to the authorization server.
The AuthorizationCodeGrant SHOULD save the code_challenge
and
code_challenge_method
into database when save_authorization_code
.
Then register this extension via:
server.register_grant(
AuthorizationCodeGrant,
[CodeChallenge(required=True)]
)
DEFAULT_CODE_CHALLENGE_METHOD
= 'plain'¶defaults to “plain” if not present in the request
SUPPORTED_CODE_CHALLENGE_METHOD
= ['plain', 'S256']¶supported code_challenge_method
Get “code_challenge” associated with this authorization code. Developers MAY re-implement it in subclass, the default logic:
def get_authorization_code_challenge(self, authorization_code):
return authorization_code.code_challenge
Parameters: | authorization_code – the instance of authorization_code |
---|
Get “code_challenge_method” associated with this authorization code. Developers MAY re-implement it in subclass, the default logic:
def get_authorization_code_challenge_method(self, authorization_code):
return authorization_code.code_challenge_method
Parameters: | authorization_code – the instance of authorization_code |
---|
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.
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, client):
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()
if tok:
if tok.client_id == client.client_id:
return tok
if has_introspect_permission(client):
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,
}
# 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)
authlib.oauth2.rfc7662.
IntrospectionEndpoint
(server)¶Implementation of introspection endpoint which is described in RFC7662.
ENDPOINT_NAME
= 'introspection'¶Endpoint name to be registered
authenticate_endpoint_credential
(request, client)¶The protected resource calls the introspection endpoint using an HTTP
POST
request with parameters sent as
“application/x-www-form-urlencoded” data. The protected resource sends a
parameter representing the token along with optional parameters
representing additional context that is known by the protected resource
to aid the authorization server in its response.
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.create_endpoint_response
(request)¶Validate introspection request and create the response.
Returns: | (status_code, body, headers) |
---|
query_token
(token, token_type_hint, client)¶Get the token from database/storage by the given token string. Developers should implement this method:
def query_token(self, token, token_type_hint, client):
if token_type_hint == 'access_token':
tok = Token.query_by_access_token(token)
elif token_type_hint == 'refresh_token':
tok = Token.query_by_refresh_token(token)
else:
tok = Token.query_by_access_token(token)
if not tok:
tok = Token.query_by_refresh_token(token)
if check_client_permission(client, tok):
return tok
introspect_token
(token)¶Read given token and return its introspection metadata as a dictionary following Section 2.2:
def introspect_token(self, token):
active = is_token_active(token)
return {
'active': active,
'client_id': token.client_id,
'token_type': token.token_type,
'username': get_token_username(token),
'scope': token.get_scope(),
'sub': get_token_user_sub(token),
'aud': token.client_id,
'iss': 'https://server.example.com/',
'exp': token.expires_at,
'iat': token.issued_at,
}
authenticate_endpoint_client
(request)¶Authentication client for endpoint with CLIENT_AUTH_METHODS
.
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)
authlib.jose.
OKPKey
(payload)¶Key class of the OKP
key type.
as_pem
(is_private=False, password=None)¶Export key into PEM format bytes.
Parameters: |
|
---|---|
Returns: | bytes |
import_key
(raw, options=None)¶Import a key from PEM or dict data.
AuthorizationServerMetadata
is enabled by default in
framework integrations:
In Flask OAuth 2.0 Server, config with:
OAUTH2_METADATA_FILE = '/www/.well-known/oauth-authorization-server'
In Django OAuth 2.0 Server, add into settings:
AUTHLIB_OAUTH2_PROVIDER = {
'metadata_file': '/www/.well-known/oauth-authorization-server'
}
authlib.oauth2.rfc8414.
AuthorizationServerMetadata
¶Define Authorization Server Metadata via Section 2 in RFC8414.
validate_issuer
()¶REQUIRED. The authorization server’s issuer identifier, which is a URL that uses the “https” scheme and has no query or fragment components.
URL of the authorization server’s authorization endpoint [RFC6749]. This is REQUIRED unless no grant types are supported that use the authorization endpoint.
validate_token_endpoint
()¶URL of the authorization server’s token endpoint [RFC6749]. This is REQUIRED unless only the implicit grant type is supported.
validate_jwks_uri
()¶OPTIONAL. URL of the authorization server’s JWK Set [JWK] document. The referenced document contains the signing key(s) the client uses to validate signatures from the authorization server. This URL MUST use the “https” scheme. The JWK Set MAY also contain the server’s encryption key or keys, which are used by clients to encrypt requests to the server. When both signing and encryption keys are made available, a “use” (public key use) parameter value is REQUIRED for all keys in the referenced JWK Set to indicate each key’s intended usage.
validate_registration_endpoint
()¶OPTIONAL. URL of the authorization server’s OAuth 2.0 Dynamic Client Registration endpoint [RFC7591].
validate_scopes_supported
()¶RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] “scope” values that this authorization server supports. Servers MAY choose not to advertise some supported scope values even when this parameter is used.
validate_response_types_supported
()¶REQUIRED. JSON array containing a list of the OAuth 2.0 “response_type” values that this authorization server supports. The array values used are the same as those used with the “response_types” parameter defined by “OAuth 2.0 Dynamic Client Registration Protocol” [RFC7591].
validate_response_modes_supported
()¶OPTIONAL. JSON array containing a list of the OAuth 2.0 “response_mode” values that this authorization server supports, as specified in “OAuth 2.0 Multiple Response Type Encoding Practices” [OAuth.Responses]. If omitted, the default is “[“query”, “fragment”]”. The response mode value “form_post” is also defined in “OAuth 2.0 Form Post Response Mode” [OAuth.Post].
validate_grant_types_supported
()¶OPTIONAL. JSON array containing a list of the OAuth 2.0 grant type values that this authorization server supports. The array values used are the same as those used with the “grant_types” parameter defined by “OAuth 2.0 Dynamic Client Registration Protocol” [RFC7591]. If omitted, the default value is “[“authorization_code”, “implicit”]”.
validate_token_endpoint_auth_methods_supported
()¶OPTIONAL. JSON array containing a list of client authentication methods supported by this token endpoint. Client authentication method values are used in the “token_endpoint_auth_method” parameter defined in Section 2 of [RFC7591]. If omitted, the default is “client_secret_basic” – the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749].
validate_token_endpoint_auth_signing_alg_values_supported
()¶OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the token endpoint for the signature on the JWT [JWT] used to authenticate the client at the token endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “token_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. Servers SHOULD support “RS256”. The value “none” MUST NOT be used.
validate_service_documentation
()¶OPTIONAL. URL of a page containing human-readable information that developers might want or need to know when using the authorization server. In particular, if the authorization server does not support Dynamic Client Registration, then information on how to register clients needs to be provided in this documentation.
validate_ui_locales_supported
()¶OPTIONAL. Languages and scripts supported for the user interface, represented as a JSON array of language tag values from BCP 47 [RFC5646]. If omitted, the set of supported languages and scripts is unspecified.
validate_op_policy_uri
()¶OPTIONAL. URL that the authorization server provides to the person registering the client to read about the authorization server’s requirements on how the client can use the data provided by the authorization server. The registration process SHOULD display this URL to the person registering the client if it is given. As described in Section 5, despite the identifier “op_policy_uri” appearing to be OpenID-specific, its usage in this specification is actually referring to a general OAuth 2.0 feature that is not specific to OpenID Connect.
validate_op_tos_uri
()¶OPTIONAL. URL that the authorization server provides to the person registering the client to read about the authorization server’s terms of service. The registration process SHOULD display this URL to the person registering the client if it is given. As described in Section 5, despite the identifier “op_tos_uri”, appearing to be OpenID-specific, its usage in this specification is actually referring to a general OAuth 2.0 feature that is not specific to OpenID Connect.
validate_revocation_endpoint
()¶OPTIONAL. URL of the authorization server’s OAuth 2.0 revocation endpoint [RFC7009].
validate_revocation_endpoint_auth_methods_supported
()¶OPTIONAL. JSON array containing a list of client authentication methods supported by this revocation endpoint. The valid client authentication method values are those registered in the IANA “OAuth Token Endpoint Authentication Methods” registry [IANA.OAuth.Parameters]. If omitted, the default is “client_secret_basic” – the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749].
validate_revocation_endpoint_auth_signing_alg_values_supported
()¶OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the revocation endpoint for the signature on the JWT [JWT] used to authenticate the client at the revocation endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “revocation_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. The value “none” MUST NOT be used.
validate_introspection_endpoint
()¶OPTIONAL. URL of the authorization server’s OAuth 2.0 introspection endpoint [RFC7662].
validate_introspection_endpoint_auth_methods_supported
()¶OPTIONAL. JSON array containing a list of client authentication methods supported by this introspection endpoint. The valid client authentication method values are those registered in the IANA “OAuth Token Endpoint Authentication Methods” registry [IANA.OAuth.Parameters] or those registered in the IANA “OAuth Access Token Types” registry [IANA.OAuth.Parameters]. (These values are and will remain distinct, due to Section 7.2.) If omitted, the set of supported authentication methods MUST be determined by other means.
validate_introspection_endpoint_auth_signing_alg_values_supported
()¶OPTIONAL. JSON array containing a list of the JWS signing algorithms (“alg” values) supported by the introspection endpoint for the signature on the JWT [JWT] used to authenticate the client at the introspection endpoint for the “private_key_jwt” and “client_secret_jwt” authentication methods. This metadata entry MUST be present if either of these authentication methods are specified in the “introspection_endpoint_auth_methods_supported” entry. No default algorithms are implied if this entry is omitted. The value “none” MUST NOT be used.
validate_code_challenge_methods_supported
()¶OPTIONAL. JSON array containing a list of Proof Key for Code Exchange (PKCE) [RFC7636] code challenge methods supported by this authorization server. Code challenge method values are used in the “code_challenge_method” parameter defined in Section 4.3 of [RFC7636]. The valid code challenge method values are those registered in the IANA “PKCE Code Challenge Methods” registry [IANA.OAuth.Parameters]. If omitted, the authorization server does not support PKCE.
validate
()¶Validate all server metadata value.
This section contains the generic implementation of RFC8628. OAuth 2.0 Device Authorization Grant is usually used when devices have limited input capabilities or lack a suitable browser, such as smart TVs, media consoles, picture frames, printers and etc.
To integrate with Authlib Flask OAuth 2.0 Server or Django OAuth 2.0 Server, developers MUST implement the missing methods of the two classes:
There are two missing methods that developers MUST implement:
from authlib.oauth2.rfc8628 import DeviceAuthorizationEndpoint
class MyDeviceAuthorizationEndpoint(DeviceAuthorizationEndpoint):
def get_verification_uri(self):
return 'https://example.com/active'
def save_device_credential(self, client_id, scope, data):
credential = DeviceCredential(
client_id=client_id,
scope=scope,
**data
)
credential.save()
# register it to authorization server
authorization_server.register_endpoint(MyDeviceAuthorizationEndpoint)
get_verification_uri
is the URL that end user will use their browser to
log in and authenticate. See below “Verification Endpoint”.
After the registration, you can create a response with:
@app.route('/device_authorization', methods=['POST'])
def device_authorization():
return server.create_endpoint_response('device_authorization')
With Authlib .register_grant
, we can add DeviceCodeGrant
easily.
But first, we need to implement the missing methods:
from authlib.oauth2.rfc8628 import DeviceCodeGrant
class MyDeviceCodeGrant(DeviceCodeGrant):
def query_device_credential(self, device_code):
return DeviceCredential.query(device_code=device_code)
def query_user_grant(self, user_code):
data = redis.get('oauth_user_grant:' + user_code)
if not data:
return None
user_id, allowed = data.split()
user = User.query.get(user_id)
return user, bool(allowed)
def should_slow_down(self, credential, now):
# developers can return True/False based on credential and now
return False
authorization_server.register_grant(MyDeviceCodeGrant)
Note query_user_grant
, we are fetching data from redis. This data
was saved from verification endpoint when end user granted the request.
Developers MUST implement this part by themselves. Here is a hint on how to implement this endpoint:
@app.route('/active', methods=['GET', 'POST'])
@login_required
def verify_device_code():
if request.method == 'GET':
return render_template('verification.html')
allowed = request.form['allowed']
user_code = request.form['user_code']
key = 'oauth_user_grant:' + user_code
redis.set(key, f'{current_user.id} {allowed}', 12)
return render_template('verification.html')
Check points:
get_verification_uri
in Device Authorization Endpointquery_user_grant
in Device Code Grantauthlib.oauth2.rfc8628.
DeviceAuthorizationEndpoint
(server)¶This OAuth 2.0 [RFC6749] protocol extension enables OAuth clients to request user authorization from applications on devices that have limited input capabilities or lack a suitable browser. Such devices include smart TVs, media consoles, picture frames, and printers, which lack an easy input method or a suitable browser required for traditional OAuth interactions. Here is the authorization flow:
+----------+ +----------------+
| |>---(A)-- Client Identifier --->| |
| | | |
| |<---(B)-- Device Code, ---<| |
| | User Code, | |
| Device | & Verification URI | |
| Client | | |
| | [polling] | |
| |>---(E)-- Device Code --->| |
| | & Client Identifier | |
| | | Authorization |
| |<---(F)-- Access Token ---<| Server |
+----------+ (& Optional Refresh Token) | |
v | |
: | |
(C) User Code & Verification URI | |
: | |
v | |
+----------+ | |
| End User | | |
| at |<---(D)-- End user reviews --->| |
| Browser | authorization request | |
+----------+ +----------------+
This DeviceAuthorizationEndpoint is the implementation of step (A) and (B).
USER_CODE_TYPE
= 'string'¶customize “user_code” type, string or digital
EXPIRES_IN
= 1800¶The lifetime in seconds of the “device_code” and “user_code”
INTERVAL
= 5¶The minimum amount of time in seconds that the client SHOULD wait between polling requests to the token endpoint.
generate_user_code
()¶A method to generate user_code
value for device authorization
endpoint. This method will generate a random string like MQNA-JPOZ.
Developers can rewrite this method to create their own user_code
.
generate_device_code
()¶A method to generate device_code
value for device authorization
endpoint. This method will generate a random string of 42 characters.
Developers can rewrite this method to create their own device_code
.
get_verification_uri
()¶Define the verification_uri
of device authorization endpoint.
Developers MUST implement this method in subclass:
def get_verification_uri(self):
return 'https://your-company.com/active'
save_device_credential
(client_id, scope, data)¶Save device token into database for later use. Developers MUST implement this method in subclass:
def save_device_credential(self, client_id, scope, data):
item = DeviceCredential(
client_id=client_id,
scope=scope,
**data
)
item.save()
authlib.oauth2.rfc8628.
DeviceCodeGrant
(request, server)¶This OAuth 2.0 [RFC6749] protocol extension enables OAuth clients to request user authorization from applications on devices that have limited input capabilities or lack a suitable browser. Such devices include smart TVs, media consoles, picture frames, and printers, which lack an easy input method or a suitable browser required for traditional OAuth interactions. Here is the authorization flow:
+----------+ +----------------+
| |>---(A)-- Client Identifier --->| |
| | | |
| |<---(B)-- Device Code, ---<| |
| | User Code, | |
| Device | & Verification URI | |
| Client | | |
| | [polling] | |
| |>---(E)-- Device Code --->| |
| | & Client Identifier | |
| | | Authorization |
| |<---(F)-- Access Token ---<| Server |
+----------+ (& Optional Refresh Token) | |
v | |
: | |
(C) User Code & Verification URI | |
: | |
v | |
+----------+ | |
| End User | | |
| at |<---(D)-- End user reviews --->| |
| Browser | authorization request | |
+----------+ +----------------+
This DeviceCodeGrant is the implementation of step (E) and (F).
validate_token_request
()¶After displaying instructions to the user, the client creates an access token request and sends it to the token endpoint with the following parameters:
For example, the client makes the following HTTPS request:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=1406020730
create_token_response
()¶If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token.
authenticate_token_endpoint_client
()¶Authenticate client with the given methods for token endpoint.
For example, the client makes the following HTTP request using TLS:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
Default available methods are: “none”, “client_secret_basic” and “client_secret_post”.
Returns: | client |
---|
query_device_credential
(device_code)¶Get device credential from previously savings via DeviceAuthorizationEndpoint
.
Developers MUST implement it in subclass:
def query_device_credential(self, device_code):
return DeviceCredential.query.get(device_code)
Parameters: | device_code – a string represent the code. |
---|---|
Returns: | DeviceCredential instance |
query_user_grant
(user_code)¶Get user and grant via the given user code. Developers MUST implement it in subclass:
def query_user_grant(self, user_code):
# e.g. we saved user grant info in redis
data = redis.get('oauth_user_grant:' + user_code)
if not data:
return None
user_id, allowed = data.split()
user = User.query.get(user_id)
return user, bool(allowed)
Note, user grant information is saved by verification endpoint.
should_slow_down
(credential, now)¶The authorization request is still pending and polling should continue, but the interval MUST be increased by 5 seconds for this and all subsequent requests.
save_token
(token)¶A method to save token into database.
validate_requested_scope
()¶Validate if requested scope is supported by Authorization Server.
This part of the documentation covers the specification of OpenID Connect. Learn how to use it in Flask OIDC Provider and Django OIDC Provider.
authlib.oidc.core.grants.
OpenIDCode
(require_nonce=False)¶Bases: object
An extension from OpenID Connect for “grant_type=code” request.
exists_nonce
(nonce, request)¶Check if the given nonce is existing in your database. Developers MUST implement this method in subclass, e.g.:
def exists_nonce(self, nonce, request):
exists = AuthorizationCode.query.filter_by(
client_id=request.client_id, nonce=nonce
).first()
return bool(exists)
Parameters: |
|
---|---|
Returns: | Boolean |
generate_user_info
(user, scope)¶Provide user information for the given scope. Developers MUST implement this method in subclass, e.g.:
from authlib.oidc.core import UserInfo
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
Parameters: |
|
---|---|
Returns: |
|
get_audiences
(request)¶Parse aud value for id_token, default value is client id. Developers MAY rewrite this method to provide a customized audience value.
get_jwt_config
(grant)¶Get the JWT configuration for OpenIDCode extension. The JWT
configuration will be used to generate id_token
. Developers
MUST implement this method in subclass, e.g.:
def get_jwt_config(self, grant):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'issuer-identity',
'exp': 3600
}
Parameters: | grant – AuthorizationCodeGrant instance |
---|---|
Returns: | dict |
authlib.oidc.core.grants.
OpenIDImplicitGrant
(request, server)¶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.
The authorization server MUST NOT issue a refresh token.
For example, the authorization server redirects the user-agent by sending the following HTTP response:
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
&state=xyz&token_type=example&expires_in=3600
Developers should note that some user-agents do not support the inclusion of a fragment component in the HTTP “Location” response header field. Such clients will require using other methods for redirecting the client than a 3xx redirection response – for example, returning an HTML page that includes a ‘continue’ button with an action linked to the redirection URI.
Parameters: |
|
---|---|
Returns: | (status_code, body, headers) |
exists_nonce
(nonce, request)¶Check if the given nonce is existing in your database. Developers should implement this method in subclass, e.g.:
def exists_nonce(self, nonce, request):
exists = AuthorizationCode.query.filter_by(
client_id=request.client_id, nonce=nonce
).first()
return bool(exists)
Parameters: |
|
---|---|
Returns: | Boolean |
generate_user_info
(user, scope)¶Provide user information for the given scope. Developers MUST implement this method in subclass, e.g.:
from authlib.oidc.core import UserInfo
def generate_user_info(self, user, scope):
user_info = UserInfo(sub=user.id, name=user.name)
if 'email' in scope:
user_info['email'] = user.email
return user_info
Parameters: |
|
---|---|
Returns: |
|
get_audiences
(request)¶Parse aud value for id_token, default value is client id. Developers MAY rewrite this method to provide a customized audience value.
get_jwt_config
()¶Get the JWT configuration for OpenIDImplicitGrant. The JWT
configuration will be used to generate id_token
. Developers
MUST implement this method in subclass, e.g.:
def get_jwt_config(self):
return {
'key': read_private_key_file(key_path),
'alg': 'RS512',
'iss': 'issuer-identity',
'exp': 3600
}
Returns: | 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.
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
authlib.oidc.core.grants.
OpenIDHybridGrant
(request, server)¶Bases: authlib.oidc.core.grants.implicit.OpenIDImplicitGrant
AUTHORIZATION_CODE_LENGTH
= 48¶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
item = 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,
)
item.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.
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
authlib.oidc.core.
IDToken
(payload, header, options=None, params=None)¶Bases: authlib.jose.rfc7519.claims.JWTClaims
validate
(now=None, leeway=0)¶Validate everything in claims payload.
validate_acr
()¶OPTIONAL. Authentication Context Class Reference. String specifying an Authentication Context Class Reference value that identifies the Authentication Context Class that the authentication performed satisfied. The value “0” indicates the End-User authentication did not meet the requirements of ISO/IEC 29115 level 1. Authentication using a long-lived browser cookie, for instance, is one example where the use of “level 0” is appropriate. Authentications with level 0 SHOULD NOT be used to authorize access to any resource of any monetary value. An absolute URI or an RFC 6711 registered name SHOULD be used as the acr value; registered names MUST NOT be used with a different meaning than that which is registered. Parties using this claim will need to agree upon the meanings of the values used, which may be context-specific. The acr value is a case sensitive string.
validate_amr
()¶OPTIONAL. Authentication Methods References. JSON array of strings that are identifiers for authentication methods used in the authentication. For instance, values might indicate that both password and OTP authentication methods were used. The definition of particular values to be used in the amr Claim is beyond the scope of this specification. Parties using this claim will need to agree upon the meanings of the values used, which may be context-specific. The amr value is an array of case sensitive strings.
validate_at_hash
()¶OPTIONAL. Access Token hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token’s JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string.
validate_auth_time
()¶Time when the End-User authentication occurred. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time. When a max_age request is made or when auth_time is requested as an Essential Claim, then this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL.
validate_azp
()¶OPTIONAL. Authorized party - the party to which the ID Token was issued. If present, it MUST contain the OAuth 2.0 Client ID of this party. This Claim is only needed when the ID Token has a single audience value and that audience is different than the authorized party. It MAY be included even when the authorized party is the same as the sole audience. The azp value is a case sensitive string containing a StringOrURI value.
validate_nonce
()¶String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token. If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request. If present in the Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token with the Claim Value being the nonce value sent in the Authentication Request. Authorization Servers SHOULD perform no other processing on nonce values used. The nonce value is a case sensitive string.
authlib.oidc.core.
CodeIDToken
(payload, header, options=None, params=None)¶Bases: authlib.oidc.core.claims.IDToken
authlib.oidc.core.
ImplicitIDToken
(payload, header, options=None, params=None)¶Bases: authlib.oidc.core.claims.IDToken
validate_at_hash
()¶If the ID Token is issued from the Authorization Endpoint with an access_token value, which is the case for the response_type value id_token token, this is REQUIRED; it MAY NOT be used when no Access Token is issued, which is the case for the response_type value id_token.
authlib.oidc.core.
HybridIDToken
(payload, header, options=None, params=None)¶Bases: authlib.oidc.core.claims.ImplicitIDToken
validate
(now=None, leeway=0)¶Validate everything in claims payload.
validate_c_hash
()¶Code hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token’s JOSE Header. For instance, if the alg is HS512, hash the code value with SHA-512, then take the left-most 256 bits and base64url encode them. The c_hash value is a case sensitive string. If the ID Token is issued from the Authorization Endpoint with a code, which is the case for the response_type values code id_token and code id_token token, this is REQUIRED; otherwise, its inclusion is OPTIONAL.
authlib.oidc.core.
UserInfo
¶The standard claims of a UserInfo object. Defined per Section 5.1.
REGISTERED_CLAIMS
= ['sub', 'name', 'given_name', 'family_name', 'middle_name', 'nickname', 'preferred_username', 'profile', 'picture', 'website', 'email', 'email_verified', 'gender', 'birthdate', 'zoneinfo', 'locale', 'phone_number', 'phone_number_verified', 'address', 'updated_at']¶registered claims that UserInfo supports
This section aims to make Authlib sustainable, on governance, code commits, issues and finance.
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:
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:
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:
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.
An official example on how to create an OAuth 2.0 server with Authlib.
An official example on how to create an OpenID Connect server with Authlib.
Open source projects that are using Authlib to create an OAuth server.
Customizable and skinnable social platform dedicated to (open)data.
AMWA NMOS BCP-003-02 Authorisation Server
A service that analyzes docker images and applies user-defined acceptance policies to allow automated container image validation and certification.
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:
Authlib is accepting sponsorships via GitHub Sponsors or Patreon. You are welcome to become a backer or a sponsor.
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 is written and maintained by Hsiaoming Yang.
Here is the full list of the main contributors:
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:
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 Oct 14, 2020.
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:
We also fixed bugs for integrations:
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.
none
auth method for authorization code by default.code_verifier
via issue#216.introspect_token
method on OAuth 2 Client via issue#224.Released on May 6, 2020.
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:
nonce
via issue#180.get_headers
leak.code_verifier
via issue#165.Breaking Change: drop sync OAuth clients of HTTPX.
Released on Nov 11, 2019. Go Async
This is the release that makes Authlib one more step close to v1.0. We
did a huge refactor on our integrations. Authlib believes in monolithic
design, it enables us to design the API to integrate with every framework
in the best way. In this release, Authlib has re-organized the folder
structure, moving every integration into the integrations
folder. It
makes Authlib to add more integrations easily in the future.
RFC implementations and updates in this release:
New integrations and changes in this release:
authlib.client.aiohttp
has been removedBug fixes and enhancements in this release:
alg
values easily for JWS and JWE.Deprecate Changes: find how to solve the deprecate issues via https://git.io/Jeclj
Released on Sep 3, 2019.
Breaking Change: Authlib Grant system has been redesigned. If you are creating OpenID Connect providers, please read the new documentation for OpenID Connect.
Important Update: Django OAuth 2.0 server integration is ready now. You can create OAuth 2.0 provider and OpenID Connect 1.0 with Django framework.
RFC implementations and updates in this release:
AssertionClient
for the assertion frameworkIntrospectionToken
for introspection token endpointRefactor and bug fixes in this release:
RefreshTokenGrant.revoke_old_credential
methodauthlib.client
, no breaking changesOAuth2Request
, use explicit query and formrequests
to optional dependencyAsyncAssertionClient
for aiohttpDeprecate Changes: find how to solve the deprecate issues via https://git.io/fjPsV
Released on Apr 6, 2019.
BIG NEWS: Authlib has changed its open source license from AGPL to BSD.
Important Changes: Authlib specs module has been split into jose, oauth1, oauth2, and oidc. Find how to solve the deprecate issues via https://git.io/fjvpt
RFC implementations and updates in this release:
Small changes and bug fixes in this release:
OAuth2Session
via issue#96.OAuth2Session
via PR#100, thanks
to pingz.Experiment Features: There is an experiment aiohttp
client for OAuth1
and OAuth2 in authlib.client.aiohttp
.
Find old changelog at https://github.com/lepture/authlib/releases
Consider to follow Authlib on Twitter, and subscribe Authlib Blog.