Release v0.12. (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+.
A simple Flask OAuth Client which connects to the GitHub OAuth2 API:
from flask import Flask
from authlib.flask.client import OAuth
# use loginpass to make OAuth connection simpler
from loginpass import create_flask_blueprint, GitHub
app = Flask(__name__)
oauth = OAuth(app)
def handle_authorize(remote, token, user_info):
if token:
save_token(remote.name, token)
if user_info:
save_user(user_info)
return user_page
raise some_error
github_bp = create_flask_blueprint(GitHub, oauth, handle_authorize)
app.register_blueprint(github_bp, url_prefix='/github')
OAuth server (provider) on the other hand is a little complex, find a real Flask OAuth 2.0 Server via Example of OAuth 2.0 server.
This part of the documentation begins with some background information about Authlib, and installation of Authlib.
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. Flexible has been taken
into account from the start of the project. Take OAuth 2.0 server as an
example, instead of a pre-configured server, Authlib takes advantage of
register
.
authorization_server.register_grant(AuthorizationCodeGrant)
authorization_server.register_endpoint(RevocationEndpoint)
If you find anything not that flexible, you can ask help on StackOverflow or open an issue on GitHub.
Authlib is a spec-compliant library which follows the latest specifications.
We keep the generic tool functions in a specs
module. When there is a
auth-related specification, we add it into specs
.
Currently, these specs are in the warehouse:
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/
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. Or, you can install Authlib with:
$ pip install Authlib[client]
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 .
OAuth provides a method for clients to access server resources on behalf of a resource owner (such as a different client or an end- user). It also provides a process for end-users to authorize third- party access to their server resources without sharing their credentials (typically, a username and password pair), using user- agent redirections.
This section will help developers understand the concepts in OAuth 1.0, but not in deep. Here is an overview of a typical OAuth 1.0 flow:
It takes more steps to obtain an access token than OAuth 2.0.
There are usually three roles in an OAuth 1.0 flow. Let’s take Twitter as an example, you are building a mobile app to send tweets:
During the OAuth 1.0 process, there are several credentials passed from server to client, and vice versa.
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.
In OAuth 1.0, every request client sending to server requires a signature. The signature is calculated from:
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:
This part of the documentation contains information on the client parts. For
Requests.Session
, Flask integration and Django integration.
In order to use Authlib client, you have to install requests
yourself.
You can either install requests with:
$ pip install requests
Or you can install with:
$ pip install Authlib[client]
Here is a simple overview of Flask OAuth client:
from flask import Flask, jsonify
from authlib.flask.client import OAuth
app = Flask(__name__)
oauth = OAuth(app)
github = oauth.register('github', {...})
@app.route('/login')
def login():
redirect_uri = url_for('authorize', _external=True)
return github.authorize_redirect(redirect_uri)
@app.route('/authorize')
def authorize():
token = github.authorize_access_token()
# you can save the token into database
profile = github.get('/user')
return jsonify(profile)
Follow the documentation below to find out more in detail.
The OAuth1Session
in Authlib is a subclass of requests.Session
.
It shares the same API with requests.Session
and extends it with OAuth 1
protocol. This section is a guide on how to obtain an access token in OAuth 1
flow.
Note
If you are using Flask or Django, you may have interests in Flask OAuth Client and Django OAuth Client.
If you are not familiar with OAuth 1.0, it is better to Understand OAuth 1.0 now.
There are three steps in OAuth 1 to obtain an access token. Initialize the session for reuse:
>>> from authlib.client import OAuth1Session
>>> client_id = 'Your Twitter client key'
>>> client_secret = 'Your Twitter client secret'
>>> session = OAuth1Session(client_id, client_secret)
The first step is to fetch temporary credential, which will be used to generate authorization URL:
>>> request_token_url = 'https://api.twitter.com/oauth/request_token'
>>> request_token = session.fetch_request_token(request_token_url)
>>> print(request_token)
{'oauth_token': 'gA..H', 'oauth_token_secret': 'lp..X', 'oauth_callback_confirmed': 'true'}
Save this temporary credential for later use (if required).
You can assign a redirect_uri
before fetching the request token, if
you want to redirect back to another URL other than the one you registered:
>>> session.redirect_uri = 'https://your-domain.org/auth'
>>> session.fetch_request_token(request_token_url)
The second step is to generate the authorization URL:
>>> authenticate_url = 'https://api.twitter.com/oauth/authenticate'
>>> session.create_authorization_url(authenticate_url, request_token['oauth_token'])
'https://api.twitter.com/oauth/authenticate?oauth_token=gA..H'
Actually, the second parameter request_token
can be omitted, since session
is re-used:
>>> session.create_authorization_url(authenticate_url)
Now visit the authorization url that OAuth1Session.create_authorization_url()
generated, and grant the authorization.
When the authorization is granted, you will be redirected back to your registered callback URI. For instance:
https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq
If you assigned redirect_uri
in Fetch Access Token, the
authorize response would be something like:
https://your-domain.org/auth?oauth_token=gA..H&oauth_verifier=fcg..1Dq
Now fetch the access token with this response:
>>> resp_url = 'https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq'
>>> session.parse_authorization_response(resp_url)
>>> access_token_url = 'https://api.twitter.com/oauth/access_token'
>>> token = session.fetch_access_token(access_token_url)
>>> print(token)
{
'oauth_token': '12345-st..E',
'oauth_token_secret': 'o67..X',
'user_id': '12345',
'screen_name': 'lepture',
'x_auth_expires': '0'
}
>>> save_access_token(token)
Save this token to access protected resources.
The above flow is not always what we will use in a real project. When we are redirected to authorization endpoint, our session is over. In this case, when the authorization server send us back to our server, we need to create another session:
>>> # restore your saved request token, which is a dict
>>> request_token = restore_request_token()
>>> oauth_token = request_token['oauth_token']
>>> oauth_token_secret = request_token['oauth_token_secret']
>>> session = OAuth1Session(
... client_id, client_secret,
... token=oauth_token,
... token_secret=oauth_token_secret)
>>> # there is no need for `parse_authorization_response` if you can get `verifier`
>>> verifier = request.args.get('verifier')
>>> access_token_url = 'https://api.twitter.com/oauth/access_token'
>>> token = session.fetch_access_token(access_token_url, verifier)
Now you can access the protected resources. If you re-use the session, you don’t need to do anything:
>>> account_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
>>> resp = session.get(account_url)
<Response [200]>
>>> resp.json()
{...}
The above is not the real flow, just like what we did in Fetch Access Token, we need to create another session ourselves:
>>> access_token = restore_access_token_from_database()
>>> oauth_token = access_token['oauth_token']
>>> oauth_token_secret = access_token['oauth_token_secret']
>>> session = OAuth1Session(
... client_id, client_secret,
... token=oauth_token,
... token_secret=oauth_token_secret)
>>> account_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
>>> resp = session.get(account_url)
Please note, there are duplicated steps in the documentation, read carefully and ignore the duplicated explains.
It is also possible to access protected resources with OAuth1Auth
object.
Create an instance of OAuth1Auth with an access token:
auth = OAuth1Auth(
client_id='..',
client_secret=client_secret='..',
token='oauth_token value',
token_secret='oauth_token_secret value',
...
)
Pass this auth
to requests
to access protected resources:
import requests
url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
resp = requests.get(url, auth=auth)
The OAuth2Session
in Authlib was designed to be compatible with
the one in requests-oauthlib. But now, there are some differences.
This section is a guide on how to obtain an access token in OAuth 2 flow.
Note
This OAuth2Session
is a customized requests.Session
. It shares
the same API with requests. If you are using Flask, you may have interests
in Flask OAuth Client. If you are using Django, please read
Django OAuth Client.
If you are not familiar with OAuth 2.0, it is better to Understand OAuth 2.0 now.
There are two steps in OAuth 2 to obtain an access token with authorization code grant type. Initialize the session for reuse:
>>> from authlib.client import OAuth2Session
>>> client_id = 'Your GitHub client ID'
>>> client_secret = 'Your GitHub client secret'
>>> scope = 'user:email' # we want to fetch user's email
>>> session = OAuth2Session(client_id, client_secret, scope=scope)
You can assign a redirect_uri
in case you want to specify the callback
url.
Unlike OAuth 1, there is no request token. The first step is to jump to the remote authorization server:
>>> authorize_url = 'https://github.com/login/oauth/authorize'
>>> uri, state = session.create_authorization_url(authorize_url)
>>> print(uri)
https://github.com/login/oauth/authorize?response_type=code&client_id=c..id&scope=user%3Aemail&state=d..t
The OAuth2Session.create_authorization_url()
returns a tuple of
(uri, state)
, in real project, you should save the state for later use.
Now head over to the generated authorization url, and grant the authorization.
The authorization server will redirect you back to your site with a code and state arguments:
https://example.com/github?code=42..e9&state=d..t
Use OAuth2Session.fetch_access_token()
to obtain access token. This
method will also verify the state in case of CSRF attack:
>>> authorization_response = 'https://example.com/github?code=42..e9&state=d..t'
>>> access_token_url = 'https://github.com/login/oauth/access_token'
>>> token = session.fetch_access_token(access_token_url, authorization_response=authorization_response)
>>> print(token)
{
'access_token': 'e..ad',
'token_type': 'bearer',
'scope': 'user:email'
}
Save this token to access users’ protected resources.
In real project, this session can not be re-used since you are redirected to another website. You need to create another session yourself:
>>> state = restore_previous_state()
>>> session = OAuth2Session(client_id, client_secret, state=state)
>>> session.fetch_access_token(access_token_url, authorization_response=authorization_response)
Authlib has a built-in Flask/Django integration. Learn from them.
OAuth2Session supports implicit grant type. It can fetch the access token with
the response_type
of token
:
>>> uri, state = session.create_authorization_url(authorize_url, response_type='token')
>>> print(uri)
https://some-service.com/oauth/authorize?response_type=token&client_id=be..4d&...
Visit this link, and grant the authorization, the OAuth authoirzation server will redirect back to your redirect_uri, the response url would be something like:
https://example.com/cb#access_token=2..WpA&state=xyz&token_type=bearer&expires_in=3600
Fetch access token from the fragment with OAuth2Session.fetch_access_token()
:
>>> token = session.fetch_access_token(authorization_response=authorization_response)
>>> # if you don't specify access token endpoint, it will fetch from fragment.
>>> print(token)
{'access_token': '2..WpA', 'token_type': 'bearer', 'expires_in': 3600}
Note
GitHub doesn’t support token
response type, try with other services.
The password
grant type is supported since Version 0.5. Use username
and password
to fetch the access token:
>>> token = session.fetch_access_token(token_url, username='a-name', password='a-password')
The client_credentials
grant type is supported since Version 0.5. If no
code
or no user info provided, it would be a client_credentials
request. But it is suggested that you specify a grant_type
for it:
>>> token = session.fetch_access_token(token_url)
>>> # or with grant_type
>>> token = session.fetch_access_token(token_url, grant_type='client_credentials')
When fetching access token, the authorization server will require a client
authentication, Authlib has provided a OAuth2ClientAuth
which
supports 3 methods defined by RFC7591:
The default value is client_secret_basic
. You can change the auth method
with token_endpoint_auth_method
:
>>> session = OAuth2Session(token_endpoint_auth_method='client_secret_post')
If the authorization server requires other means of authentication, you can
construct an auth
of requests, and pass it to fetch_access_token
:
>>> auth = YourAuth(...)
>>> token = session.fetch_access_token(token_url, auth=auth, ...)
It is also possible to extend the client authentication method with
register_client_auth_method()
. Besides the default
three authentication methods, there are more provided by Authlib. e.g.
These two methods are defined by RFC7523 and OpenID Connect. Find more in Using JWTs for Client Authentication.
Now you can access the protected resources. If you re-use the session, you don’t need to do anything:
>>> account_url = 'https://api.github.com/user'
>>> resp = session.get(account_url)
<Response [200]>
>>> resp.json()
{...}
The above is not the real flow, just like what we did in Fetch Access Token, we need to create another session ourselves:
>>> token = restore_access_token_from_database()
>>> # token is a dict which must contain ``access_token``, ``token_type``
>>> session = OAuth2Session(client_id, client_secret, token=token)
>>> account_url = 'https://api.github.com/user'
>>> resp = session.get(account_url)
There are services that claimed they are providing OAuth API, but with a little differences. Some services even return with the wrong Content Type. Compliance hooks are provided to solve those problems:
access_token_response
: invoked before token parsing.refresh_token_response
: invoked before refresh token parsing.protected_request
: invoked before making a request.For instance, linkedin is using a oauth2_access_token
parameter in query
string to protect users’ resources, let’s fix it:
from authlib.common.urls import add_params_to_uri
def _non_compliant_param_name(url, headers, data):
access_token = session.token.get('access_token')
token = [('oauth2_access_token', access_token)]
url = add_params_to_uri(url, token)
return url, headers, data
session.register_compliance_hook(
'protected_request', _non_compliant_param_name)
If you find a non standard OAuth 2 services, and you can’t fix it. Please report it in GitHub issues.
For services that support OpenID Connect, if a scope of openid
is provided,
the authorization server will return a value of id_token
in response:
>>> from authlib.client import OAuth2Session
>>> client_id = 'Your Google client ID'
>>> client_secret = 'Your Google client secret'
>>> scope = 'openid email profile'
>>> session = OAuth2Session(client_id, client_secret, scope=scope)
The remote server may require other parameters for OpenID Connect requests, for
instance, it may require a nonce
parameter, in thise case, you need to
generate it yourself, and pass it to create_authorization_url
:
>>> from authlib.common.security import generate_token
>>> # remember to save this nonce for verification
>>> nonce = generate_token()
>>> session.create_authorization_url(url, redirect_uri='xxx', nonce=nonce, ...)
At the last step of session.fetch_access_token
, the return value contains
a id_token
:
>>> resp = session.fetch_access_token(...)
>>> print(resp['id_token'])
This id_token
is a JWT text, it can not be used unless it is parsed.
Authlib has provided tools for parsing and validating OpenID Connect id_token:
>>> from authlib.oidc.core import CodeIDToken
>>> from authlib.jose import jwt
>>> # GET keys from https://www.googleapis.com/oauth2/v3/certs
>>> claims = jwt.decode(resp['id_token'], keys, claims_cls=CodeIDToken)
>>> claims.validate()
Get deep inside with JWT
and
CodeIDToken
. Learn how to validate JWT claims
at JSON Web Token (JWT).
AssertionSession
is a Requests Session for Assertion Framework of
OAuth 2.0 Authorization Grants. It is also know as service account. A
configured AssertionSession
with handle token authorization automatically,
which means you can just use it.
Take Google Service Account as an example, with the information in your service account JSON configure file:
import json
from authlib.client import AssertionSession
with open('MyProject-1234.json') as f:
conf = json.load(f)
token_url = conf['token_uri']
header = {'alg': 'RS256'}
key_id = conf.get('private_key_id')
if key_id:
header['kid'] = key_id
# Google puts scope in payload
claims = {'scope': scope}
session = AssertionSession(
grant_type=cls.JWT_BEARER_GRANT_TYPE,
token_url=token_url,
issuer=conf['client_email'],
audience=token_url,
claims=claims,
subject=None,
key=conf['private_key'],
header=header,
)
session.get(...)
session.post(...)
There is a ready to use GoogleServiceAccount
in loginpass. You can
also read these posts:
Looking for OAuth providers?
Flask OAuth client can handle OAuth 1 and OAuth 2 services. It shares a similar API with Flask-OAuthlib, you can transfer your code from Flask-OAuthlib to Authlib with ease. Here is how to Migrate OAuth Client from Flask-OAuthlib to Authlib.
Create a registry with OAuth
object:
from authlib.flask.client import OAuth
oauth = OAuth(app)
You can 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.
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
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',
api_base_url='https://api.twitter.com/1.1/',
client_kwargs=None,
)
The first parameter in register
method is the name of the remote
application. You can access the remote application with:
oauth.twitter.get('account/verify_credentials.json')
The second parameter in register
method is configuration. Every key value
pair can be omit. They can be configured in your Flask App configuration.
Config key is formatted with {name}_{key}
in uppercase, e.g.
TWITTER_CLIENT_ID | Twitter Consumer Key |
TWITTER_CLIENT_SECRET | Twitter Consumer Secret |
TWITTER_REQUEST_TOKEN_URL | URL to fetch OAuth request token |
If you register your remote app as oauth.register('example', ...)
, the
config key would look like:
EXAMPLE_CLIENT_ID | 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 OAuth2SessionThe {name}_CLIENT_KWARGS
is a dict configuration to pass extra parameters to
OAuth1Session
. If you are using RSA-SHA1
signature method:
EXAMPLE_CLIENT_KWARGS = {
'signature_method': 'RSA-SHA1',
'signature_type': 'HEADER',
'rsa_key': 'Your-RSA-Key'
}
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.
Our OAuth
registry provided a simple way to store temporary credentials, 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)
If cache system is not available, you can define methods for retrieving and saving request token:
def save_request_token(token):
save_request_token_to_someplace(current_user, token)
def fetch_request_token():
return get_request_token_from_someplace(current_user)
# register the two methods
oauth.register('twitter',
client_id='Twitter Consumer Key',
client_secret='Twitter Consumer Secret',
request_token_url='https://api.twitter.com/oauth/request_token',
request_token_params=None,
access_token_url='https://api.twitter.com/oauth/access_token',
access_token_params=None,
refresh_token_url=None,
authorize_url='https://api.twitter.com/oauth/authenticate',
api_base_url='https://api.twitter.com/1.1/',
client_kwargs=None,
# NOTICE HERE
save_request_token=save_request_token,
fetch_request_token=fetch_request_token,
)
After configuration of 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:
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('/')
After user confirmed on Twitter authorization page, it will redirect
back to your website /authorize
. 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
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',
authorize_url='https://github.com/login/oauth/authorize',
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:
oauth.github.get('user')
The second parameter in register
method is configuration. Every key value
pair can be omit. They can be configured in your Flask App configuration.
Config key is formatted with {name}_{key}
in uppercase, e.g.
GITHUB_CLIENT_ID | GitHub Client ID |
GITHUB_CLIENT_SECRET | GitHub Client Secret |
If you register your remote app as oauth.register('example', ...)
, the
config key would look like:
EXAMPLE_CLIENT_ID | OAuth 2 Client ID |
EXAMPLE_CLIENT_SECRET | OAuth 2 Client Secret |
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}_ACCESS_TOKEN_URL
: Access Token endpoint for OAuth 1 and OAuth 2{name}_ACCESS_TOKEN_PARAMS
: Extra parameters for Access Token endpoint{name}_REFRESH_TOKEN_URL
: Refresh Token endpoint for OAuth 2 (if any){name}_REFRESH_TOKEN_PARAMS
: Extra parameters for Refresh Token endpoint{name}_AUTHORIZE_URL
: Endpoint for user authorization of OAuth 1 ro OAuth 2{name}_AUTHORIZE_PARAMS
: Extra parameters for Authorization Endpoint.{name}_API_BASE_URL
: A base URL endpoint to make requests simple{name}_CLIENT_KWARGS
: Extra keyword arguments for OAuth1Session or OAuth2SessionThe {name}_CLIENT_KWARGS
is a dict configuration to pass extra parameters to
OAuth2Session
, you can pass extra parameters like:
EXAMPLE_CLIENT_KWARGS = {
'scope': 'profile',
'token_endpoint_auth_method': 'client_secret_basic',
'token_placement': 'header',
}
There are several token_endpoint_auth_method
, get a deep inside the
Client Authentication Methods.
After configuration of 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:
from flask import url_for, render_template
@app.route('/login')
def login():
redirect_uri = url_for('authorize', _external=True)
return oauth.github.authorize_redirect(redirect_uri)
@app.route('/authorize')
def authorize():
token = oauth.github.authorize_access_token()
resp = oauth.github.get('user')
profile = resp.json()
# do something with the token and profile
return redirect('/')
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.
There are also chances that you need to access your user’s 3rd party OAuth provider resources. For instance, you want to display your user’s GitHub profile:
@app.route('/github/<username>')
def github_profile(username):
user = User.get_by_username(username)
token = OAuth2Token.get(user_id=user.id, name='github')
# API URL: https://api.github.com/user
resp = oauth.github.get('user', token=token.to_token())
profile = resp.json()
return render_template('github.html', profile=profile)
In this case, we need a place to store the access token in order to use it later. Take an example, we want to save user’s access token into database.
Here is an example on database schema design with Flask-SQLAlchemy. We designed two tables, one is for OAuth 1, one is for OAuth 2:
class OAuth1Token(db.Model)
user_id = Column(Integer, nullable=False)
name = Column(String(20), nullable=False)
oauth_token = Column(String(48), nullable=False)
oauth_token_secret = Column(String(48))
def to_token(self):
return dict(
oauth_token=self.access_token,
oauth_token_secret=self.alt_token,
)
class OAuth2Token(db.Model):
user_id = Column(Integer, nullable=False)
name = Column(String(20), nullable=False)
token_type = Column(String(20))
access_token = Column(String(48), nullable=False)
refresh_token = Column(String(48))
expires_at = Column(Integer, default=0)
def to_token(self):
return dict(
access_token=self.access_token,
token_type=self.token_type,
refresh_token=self.refresh_token,
expires_at=self.expires_at,
)
And then we can save user’s access token into database when user was redirected
back to our /authorize
page, like:
@app.route('/authorize')
def authorize():
token = oauth.github.authorize_access_token()
resp = oauth.github.get('user')
profile = resp.json()
user = User.get_by_github(profile)
# implement save method yourself
OAuth2Token.save('github', user, token)
return redirect('/')
You can always pass a token
parameter to the remote application request
methods, like:
oauth.twitter.get(url, token=token)
oauth.twitter.post(url, token=token)
oauth.twitter.put(url, token=token)
oauth.twitter.delete(url, token=token)
There is another implicit way to apply the token into the remote application
requests. We can connect OAuth token to the current user so that you don’t need
to pass token
every time:
def fetch_twitter_token():
token = OAuth1Token.get(name='twitter', user_id=current_user.id)
if token:
return token.to_token()
# we can registry this ``fetch_token`` with oauth.register
oauth.register(
'twitter',
# ....
fetch_token=fetch_twitter_token,
)
Now you can access current logged in user’s Twitter resource without passing
the token
parameter:
@app.route('/profile')
@require_login
def twitter_profile():
resp = oauth.twitter.get('account/verify_credentials.json')
profile = resp.json()
return render_template('twitter.html', profile=profile)
Since the OAuth
registry can contain many services, it would be good enough
to share some common methods instead of defining them one by one. Here are
some hints:
from flask import url_for, render_template
@app.route('/login/<name>')
def login(name):
client = oauth.create_client(name)
redirect_uri = url_for('authorize', name=name, _external=True)
return client.authorize_redirect(redirect_uri)
@app.route('/authorize/<name>')
def authorize(name):
client = oauth.create_client(name)
token = client.authorize_access_token()
if name in OAUTH1_SERVICES:
# this is a pseudo method, you need to implement it yourself
OAuth1Token.save(name, current_user, token)
else:
# this is a pseudo method, you need to implement it yourself
OAuth2Token.save(name, current_user, token)
return redirect(url_for('profile', name=name))
@app.route('/profile/<name>')
@require_login
def profile(name):
client = oauth.create_client(name)
resp = client.get(get_profile_url(name))
profile = resp.json()
return render_template('profile.html', profile=profile)
We can share a fetch_token
method at OAuth registry level when
initialization. Define a common fetch_token
:
def fetch_token(name):
if name in OAUTH1_SERVICES:
token = OAuth1Token.get(name=name, user_id=current_user.id)
else:
token = OAuth2Token.get(name=name, user_id=current_user.id)
if token:
return token.to_token()
# pass ``fetch_token``
oauth = OAuth(app, fetch_token=fetch_token)
# or init app later
oauth = OAuth(fetch_token=fetch_token)
oauth.init_app(app)
# or init everything later
oauth = OAuth()
oauth.init_app(app, fetch_token=fetch_token)
With this common fetch_token
in OAuth, you don’t need to design the method
for each services one by one.
In OAuth 2, there is a concept of refresh_token
, Authlib can auto refresh
access token when it is expired. If the services you are using don’t issue any
refresh_token
at all, you don’t need to do anything.
Just like fetch_token
, we can define a update_token
method for each
remote app or sharing it in OAuth registry:
def update_token(name, token):
token = OAuth2Token.get(name=name, user_id=current_user.id)
if not token:
token = OAuth2Token(name=name, user_id=current_user.id)
token.token_type = token.get('token_type', 'bearer')
token.access_token = token.get('access_token')
token.refresh_token = token.get('refresh_token')
token.expires_at = token.get('expires_at')
db.session.add(token)
db.session.commit()
return token
# pass ``update_token``
oauth = OAuth(app, update_token=update_token)
# or init app later
oauth = OAuth(update_token=update_token)
oauth.init_app(app)
# or init everything later
oauth = OAuth()
oauth.init_app(app, update_token=update_token)
Adding code_challenge
provided by RFC7636: Proof Key for Code Exchange by OAuth Public Clients is simple. You
register your remote app with a code_challenge_method
:
oauth.register('example',
client_id='Example Client ID',
client_secret='Example Client Secret',
access_token_url='https://example.com/oauth/access_token',
authorize_url='https://example.com/oauth/authorize',
api_base_url='https://api.example.com/',
client_kwargs=None,
code_challenge_method='S256',
)
Note, the only supported code_challenge_method
is S256
.
The RemoteApp
is a subclass of OAuthClient
,
they share the same logic for compliance fix. Construct a method to fix
requests session:
def slack_compliance_fix(session):
def _fix(resp):
token = resp.json()
# slack returns no token_type
token['token_type'] = 'Bearer'
resp._content = to_unicode(json.dumps(token)).encode('utf-8')
return resp
session.register_compliance_hook('access_token_response', _fix)
When OAuth.register()
a remote app, pass it in the parameters:
oauth.register(
'slack',
client_id='...',
client_secret='...',
...,
compliance_fix=slack_compliance_fix,
...
)
Find all the available compliance hooks at Compliance Fix for non Standard.
There are many built-in integrations served by loginpass, checkout the
flask_example
in loginpass project. Here is an example of GitHub:
from flask import Flask
from authlib.flask.client import OAuth
from loginpass import create_flask_blueprint, GitHub
app = Flask(__name__)
oauth = OAuth(app)
def handle_authorize(remote, token, user_info):
if token:
save_token(remote.name, token)
if user_info:
save_user(user_info)
return user_page
raise some_error
github_bp = create_flask_blueprint(GitHub, oauth, handle_authorize)
app.register_blueprint(github_bp, url_prefix='/github')
# Now, there are: ``/github/login`` and ``/github/auth``
The source code of loginpass is very simple, they are just preconfigured services integrations.
Looking for OAuth providers?
The Django client shares a similar API with Flask client. But there are
differences, since Django has no request context, you need to pass request
argument yourself.
Create a registry with OAuth
object:
from authlib.django.client import OAuth
oauth = OAuth()
The common use case for OAuth is authentication, e.g. let your users log in with Twitter, GitHub, Google etc.
For instance, Twitter is an OAuth 1.0 service, you want your users to log in your website with Twitter.
The first step is register a remote application on the OAuth
registry via
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',
api_base_url='https://api.twitter.com/1.1/',
client_kwargs=None,
)
The first parameter in register
method is the name of the remote
application. You can access the remote application with:
oauth.twitter.get('account/verify_credentials.json')
The second parameter in register
method is configuration. Every key value
pair can be omit. They can be configured from your Django settings:
AUTHLIB_OAUTH_CLIENTS = {
'twitter': {
'client_id': 'Twitter Consumer Key',
'client_secret': 'Twitter Consumer Secret',
'request_token_url': 'https://api.twitter.com/oauth/request_token',
'request_token_params': None,
'access_token_url': 'https://api.twitter.com/oauth/access_token',
'access_token_params': None,
'refresh_token_url': None,
'authorize_url': 'https://api.twitter.com/oauth/authenticate',
'api_base_url': 'https://api.twitter.com/1.1/',
'client_kwargs': None
}
}
The client_kwargs
is a dict configuration to pass extra parameters to
OAuth1Session
. If you are using RSA-SHA1
signature method:
client_kwargs = {
'signature_method': 'RSA-SHA1',
'signature_type': 'HEADER',
'rsa_key': 'Your-RSA-Key'
}
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.
After configuration of 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):
# build a full authorize callback uri
redirect_uri = request.build_absolute_uri('/authorize')
return oauth.twitter.authorize_redirect(request, redirect_uri)
def authorize(request):
token = oauth.twitter.authorize_access_token(request)
resp = oauth.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
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',
authorize_url='https://github.com/login/oauth/authorize',
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:
oauth.github.get('user')
The second parameter in register
method is configuration. Every key value
pair can be omit. They can be configured from your Django settings:
AUTHLIB_OAUTH_CLIENTS = {
'github': {
'client_id': 'GitHub Client ID',
'client_secret': 'GitHub Client Secret',
'access_token_url': 'https://github.com/login/oauth/access_token',
'authorize_url': 'https://github.com/login/oauth/authorize',
'api_base_url': 'https://api.github.com/',
'client_kwargs': {'scope': 'user:email'}
}
}
The client_kwargs
is a dict configuration to pass extra parameters to
OAuth2Session
, 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.
After configuration of 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):
# build a full authorize callback uri
redirect_uri = request.build_absolute_uri('/authorize')
return oauth.github.authorize_redirect(request, redirect_uri)
def authorize(request):
token = oauth.github.authorize_access_token(request)
resp = oauth.github.get('user')
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.
There are also chances that you need to access your user’s 3rd party OAuth provider resources. For instance, you want to display your user’s GitHub profile:
def github_profile(request):
token = OAuth2Token.objects.get(
name='github',
user=request.user
)
# API URL: https://api.github.com/user
resp = oauth.github.get('user', token=token.to_token())
profile = resp.json()
return render_template('github.html', profile=profile)
In this case, we need a place to store the access token in order to use it later. Take an example, we want to save user’s access token into database.
Authlib Django client has no built-in database model. You need to design the Token model by yourself. This is designed by intention.
Here are some hints on how to design your schema:
class OAuth1Token(models.Model):
name = models.CharField(max_length=40)
oauth_token = models.CharField(max_length=200)
oauth_token_secret = models.CharField(max_length=200)
# ...
def to_token(self):
return dict(
oauth_token=self.access_token,
oauth_token_secret=self.alt_token,
)
class OAuth2Token(models.Model):
name = models.CharField(max_length=40)
token_type = models.CharField(max_length=20)
access_token = models.CharField(max_length=200)
refresh_token = models.CharField(max_length=200)
# oauth 2 expires time
expires_at = models.DateTimeField()
# ...
def to_token(self):
return dict(
access_token=self.access_token,
token_type=self.token_type,
refresh_token=self.refresh_token,
expires_at=self.expires_at,
)
And then we can save user’s access token into database when user was redirected
back to our authorize
page, like:
def authorize(request):
token = oauth.github.authorize_access_token(request)
# OAuth2Token.save('github', token)
return redirect('/')
You can always pass a token
parameter to the remote application request
methods, like:
oauth.twitter.get(url, token=token)
oauth.twitter.post(url, token=token)
oauth.twitter.put(url, token=token)
oauth.twitter.delete(url, token=token)
But it is a little waste of code each time to fetch the token like:
data = OAuth2Token.objects.get(
name='github',
user=request.user
)
token = data.to_token()
Instead, you can implement a fetch_token
method to do that. You don’t have
to fetch token every time, you can just pass the request
instance:
def fetch_twitter_token(request):
item = OAuth1Token.objects.get(
name='twitter',
user=request.user
)
return item.to_token()
# we can registry this ``fetch_token`` with oauth.register
oauth.register(
'twitter',
# ...
fetch_token=fetch_twitter_token,
)
Developers can also pass the fetch_token
to OAuth
registry so that
they don’t have to pass a fetch_token
for each remote app. In this case,
the fetch_token
will accept two parameters:
def fetch_token(name, request):
if name in OAUTH1_SERVICES:
model = OAuth1Token
else:
model = OAuth2Token
item = model.objects.get(
name=name,
user=request.user
)
return item.to_token()
oauth = OAuth(fetch_token=fetch_token)
Now, developers don’t have to pass a token
in the HTTP requests,
instead, they can pass the request
:
def fetch_resource(request):
resp = oauth.twitter.get('account/verify_credentials.json', request=request)
profile = resp.json()
# ...
Adding code_challenge
provided by RFC7636: Proof Key for Code Exchange by OAuth Public Clients is simple. You
register your remote app with a code_challenge_method
:
oauth.register(
'example',
client_id='Example Client ID',
client_secret='Example Client Secret',
access_token_url='https://example.com/oauth/access_token',
authorize_url='https://example.com/oauth/authorize',
api_base_url='https://api.example.com/',
client_kwargs=None,
code_challenge_method='S256',
)
Note, the only supportted code_challenge_method
is S256
.
The RemoteApp
is a subclass of OAuthClient
,
they share the same logic for compliance fix. Construct a method to fix
requests session:
def slack_compliance_fix(session):
def _fix(resp):
token = resp.json()
# slack returns no token_type
token['token_type'] = 'Bearer'
resp._content = to_unicode(json.dumps(token)).encode('utf-8')
return resp
session.register_compliance_hook('access_token_response', _fix)
When OAuth.register()
a remote app, pass it in the parameters:
oauth.register(
'slack',
client_id='...',
client_secret='...',
...,
compliance_fix=slack_compliance_fix,
...
)
Find all the available compliance hooks at Compliance Fix for non Standard.
There are many built-in integrations served by loginpass, checkout the
django_example
in loginpass project. Here is an example of GitHub:
from authlib.django.client import OAuth
from loginpass import create_django_urlpatterns, GitHub
oauth = OAuth()
def handle_authorize(request, remote, token, user_info):
if token:
save_token(request, remote.name, token)
if user_info:
save_user(request, user_info)
return user_page
raise some_error
oauth_urls = create_django_urlpatterns(GitHub, oauth, handle_authorize)
# Register it in ``urls.py``
from django.urls import include, path
urlpatterns = [...]
urlpatterns.append(path('/github/', include(oauth_urls)))
# Now, there are: ``/github/login`` and ``/github/auth``
The source code of loginpass is very simple, they are just preconfigured services integrations.
New in version v0.11: This is an experimental feature.
The AsyncOAuth1Client
is located in authlib.client.aiohttp
. Authlib doesn’t
embed aiohttp
as a dependency, you need to install it yourself.
Here is an example on how you can initialize an instance of AsyncOAuth1Client
for aiohttp
:
import asyncio
from aiohttp import ClientSession
from authlib.client.aiohttp import AsyncOAuth1Client, OAuthRequest
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
async def main():
# OAuthRequest is required to handle auth
async with ClientSession(request_class=OAuthRequest) as session:
client = AsyncOAuth1Client(session, 'client_id', 'client_secret', ...)
token = await client.fetch_request_token(REQUEST_TOKEN_URL)
print(token)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
The API is similar with OAuth1Session
above. Using the client
for the
three steps authorization:
The first step is to fetch temporary credential, which will be used to generate authorization URL:
request_token_url = 'https://api.twitter.com/oauth/request_token'
request_token = await client.fetch_request_token(request_token_url)
print(request_token)
{'oauth_token': 'gA..H', 'oauth_token_secret': 'lp..X', 'oauth_callback_confirmed': 'true'}
Save this temporary credential for later use (if required).
The second step is to generate the authorization URL:
authenticate_url = 'https://api.twitter.com/oauth/authenticate'
url = client.create_authorization_url(authenticate_url, request_token['oauth_token'])
print(url)
'https://api.twitter.com/oauth/authenticate?oauth_token=gA..H'
Actually, the second parameter request_token
can be omitted, since session
is re-used:
url = client.create_authorization_url(authenticate_url)
print(url)
'https://api.twitter.com/oauth/authenticate?oauth_token=gA..H'
When the authorization is granted, you will be redirected back to your registered callback URI. For instance:
https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq
If you assigned redirect_uri
in Fetch Access Token, the
authorize response would be something like:
https://your-domain.org/auth?oauth_token=gA..H&oauth_verifier=fcg..1Dq
In the production flow, you may need to create a new instance of
AsyncOAuth1Client
, it is the same as above. You need to use the previous
request token to exchange an access token:
# twitter redirected back to your website
resp_url = 'https://example.com/twitter?oauth_token=gA..H&oauth_verifier=fcg..1Dq'
# you may use the ``oauth_token`` in resp_url to
# get back your previous request token
request_token = {'oauth_token': 'gA..H', 'oauth_token_secret': '...'}
# assign request token to client
client.token = request_token
# resolve the ``oauth_verifier`` from resp_url
oauth_verifier = get_oauth_verifier_value(resp_url)
access_token_url = 'https://api.twitter.com/oauth/access_token'
token = await client.fetch_access_token(access_token_url, oauth_verifier)
You can save the token
to access protected resources later.
Now you can access the protected resources. Usually, you will need to create
an instance of AsyncOAuth1Client
:
# get back the access token if you have saved it in some place
access_token = {'oauth_token': '...', 'oauth_secret': '...'}
# assign it to client
client.token = access_token
account_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
async with client.get(account_url) as resp:
data = await resp.json()
Notice, it is also possible to create the client instance with access token at the initialization:
client = AsyncOAuth1Client(
session, 'client_id', 'client_secret',
token='...', token_secret='...',
...
)
New in version v0.11: This is an experimental feature.
New in version v0.12: This is an experimental feature.
The AsyncAssertionClient
is located in authlib.client.aiohttp
. Authlib
doesn’t embed aiohttp
as a dependency, you need to install it yourself. It
will create a session for Assertion Framework of OAuth 2.0 Authorization Grants.
This is also know as service account.
Take Google Service Account as an example, with the information in your service account JSON configure file:
import json
import asyncio
from aiohttp import ClientSession
from authlib.client.aiohttp import OAuthRequest, AsyncAssertionClient
with open('MyProject-1234.json') as f:
conf = json.load(f)
token_url = conf['token_uri']
header = {'alg': 'RS256'}
key_id = conf.get('private_key_id')
if key_id:
header['kid'] = key_id
# Google puts scope in payload
claims = {'scope': scope}
async def main():
async with ClientSession(request_class=OAuthRequest) as session:
client = AsyncAssertionClient(
session,
token_url=token_url,
issuer=conf['client_email'],
audience=token_url,
claims=claims,
subject=None,
key=conf['private_key'],
header=header,
)
await client.get(...)
await client.post(...)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Important
This OAuthClient
is designed for developing framework integrations.
You are not supposed to use it directly, check out Flask OAuth Client
and Django OAuth Client instead.
A mixed OAuth 1 and OAuth 2 client, one to control them both. With
OAuthClient
, we make the authorization much similar. It is the
base class for framework integrations.
OAuthClient
will automatically detect whether it is OAuth 1 or
OAuth 2 via its parameters. OAuth 1 has request_token_url
, while OAuth 2
doesn’t.
To use OAuthClient for requesting user resources, you need to subclass it,
and implement a OAuthClient.get_token()
method:
class MyOAuthClient(OAuthClient):
def get_token(self):
return get_current_user_token()
Configure an OAuth 1 client with OAuthClient
:
client = OAuthClient(
client_id='Twitter Consumer Key',
client_secret='Twitter Consumer Secret',
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
authorize_url='https://api.twitter.com/oauth/authenticate',
api_base_url='https://api.twitter.com/1.1/',
)
There are other options that you could pass to the class. Please read the API documentation.
There is a request token exchange in OAuth 1, in this case, we need to save the request token before heading over to authorization endpoint:
def save_request_token(token):
session['token'] = token
# The first ``redirect_uri`` parameter is optional.
url, state = client.generate_authorize_redirect(
save_request_token=save_request_token)
Now we will get a redirect url to the authorization endpoint. The return value
is a tuple of (url, state)
, in OAuth 1, state
will always be None
.
If permission is granted, we can fetch the access token now:
def get_request_token():
return session.pop('token', None)
redirect_uri = session.pop('redirect_uri', None)
params = parse_response_url_qs()
token = client.fetch_access_token(
redirect_uri, get_request_token=get_request_token, **params)
save_token_to_db(token)
The flow of OAuth 2 is similar with OAuth 1, and much simpler:
client = OAuthClient(
client_id='GitHub Client ID',
client_secret='GitHub Client Secret',
api_base_url='https://api.github.com/',
access_token_url='https://github.com/login/oauth/access_token',
authorize_url='https://github.com/login/oauth/authorize',
client_kwargs={'scope': 'user:email'},
)
Unlike OAuth 1, there is no request token. The process to authorization server is very simple:
redirect_uri = 'https://example.com/auth'
url, state = client.generate_authorize_redirect(redirect_uri)
# save state for getting access token
session['state'] = state
Note that, in OAuth 2, there will be a state
always, you need to save it
for later use.
It’s the same as OAuth 1. If permission is granted, we can fetch the access token now:
redirect_uri = session.pop('redirect_uri', None)
params = parse_response_url_qs()
# you need to verify state here
assert params['state'] == session.pop('state')
token = client.fetch_access_token(redirect_uri, **params)
save_token_to_db(token)
Since many OAuth 2 providers are not following standard strictly, we need to fix them. It has been introduced in Compliance Fix for non Standard.
For OAuthClient, we can register our hooks one by one, with
OAuth2Session.register_compliance_hook()
:
client.session.register_compliance_hook('protected_request', func)
However, there is a shortcut attribute for it. You need to construct a method
which takes session
as the parameter:
def compliance_fix(session):
def fix_protected_request(url, headers, data):
# do something
return url, headers, data
def fix_access_token_response(response):
# patch response
return response
session.register_compliance_hook(
'protected_request', fix_protected_request)
session.register_compliance_hook(
'access_token_response', fix_access_token_response)
# register other hooks
Later, when you initialized OAuthClient, pass it to the client parameters:
client = OAuthClient(
client_id='...',
client_secret='...',
...,
compliance_fix=compliance_fix,
...
)
It will automatically patch the requests session for OAuth 2.
This part of the documentation covers the interface of Authlib Client.
authlib.client.
OAuth1Session
(client_id, client_secret=None, token=None, token_secret=None, redirect_uri=None, rsa_key=None, verifier=None, signature_method='HMAC-SHA1', signature_type='HEADER', force_include_body=False, **kwargs)¶Create 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.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.client.
OAuth2Session
(client_id=None, client_secret=None, token_endpoint_auth_method=None, refresh_token_url=None, refresh_token_params=None, scope=None, redirect_uri=None, token=None, token_placement='header', state=None, token_updater=None, **kwargs)¶Construct a new OAuth 2 client requests session.
Parameters: |
|
---|
Generate an authorization URL and state.
Parameters: |
|
---|---|
Returns: | authorization_url, state |
fetch_access_token
(url=None, **kwargs)¶Alias for fetch_token.
fetch_token
(url=None, code=None, authorization_response=None, body='', auth=None, username=None, password=None, method='POST', headers=None, **kwargs)¶Generic method for fetching an access token from the token endpoint.
Parameters: |
|
---|---|
Returns: | A |
refresh_token
(url=None, 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
(func)¶Extend client authenticate for token endpoint.
Parameters: | func – a function to sign the request |
---|
register_compliance_hook
(hook_type, hook)¶Register a hook for request/response tweaking.
Available hooks are:
revoke_token
(url, token, token_type_hint=None, body=None, auth=None, headers=None, **kwargs)¶Revoke token method defined via RFC7009.
Parameters: |
|
---|---|
Returns: | A |
authlib.client.
AssertionSession
(token_url, issuer, subject, audience, grant_type=None, claims=None, token_placement='header', scope=None, **kwargs)¶Constructs a new Assertion Framework for OAuth 2.0 Authorization Grants per RFC7521.
token_auth_class
¶alias of AssertionAuth
request
(method, url, data=None, headers=None, withhold_token=False, auth=None, **kwargs)¶Send request with auto refresh token feature.
authlib.client.
OAuth2Auth
(token, token_placement='header', client=None)¶Sign requests for OAuth 2.0, currently only bearer token is supported.
authlib.client.
OAuthClient
(client_id=None, client_secret=None, request_token_url=None, request_token_params=None, access_token_url=None, access_token_params=None, refresh_token_url=None, refresh_token_params=None, authorize_url=None, authorize_params=None, api_base_url=None, client_kwargs=None, server_metadata_url=None, compliance_fix=None, **kwargs)¶A mixed OAuth client for OAuth 1 and OAuth 2.
Parameters: |
|
---|
Create an instance of OAuthClient. If request_token_url
is configured,
it would be an OAuth 1 instance, otherwise it is OAuth 2 instance:
oauth1_client = OAuthClient(
client_id='Twitter Consumer Key',
client_secret='Twitter Consumer Secret',
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
authorize_url='https://api.twitter.com/oauth/authenticate',
api_base_url='https://api.twitter.com/1.1/',
)
oauth2_client = OAuthClient(
client_id='GitHub Client ID',
client_secret='GitHub Client Secret',
api_base_url='https://api.github.com/',
access_token_url='https://github.com/login/oauth/access_token',
authorize_url='https://github.com/login/oauth/authorize',
client_kwargs={'scope': 'user:email'},
)
Generate the authorization url and state for HTTP redirect.
Parameters: |
|
---|---|
Returns: | (url, state) |
fetch_access_token
(redirect_uri=None, request_token=None, **params)¶Fetch access token in one step.
Parameters: |
|
---|---|
Returns: | A token dict. |
get
(url, **kwargs)¶Invoke GET http request.
If api_base_url
configured, shortcut is available:
client.get('users/lepture')
post
(url, **kwargs)¶Invoke POST http request.
If api_base_url
configured, shortcut is available:
client.post('timeline', json={'text': 'Hi'})
patch
(url, **kwargs)¶Invoke PATCH http request.
If api_base_url
configured, shortcut is available:
client.patch('profile', json={'name': 'Hsiaoming Yang'})
put
(url, **kwargs)¶Invoke PUT http request.
If api_base_url
configured, shortcut is available:
client.put('profile', json={'name': 'Hsiaoming Yang'})
delete
(url, **kwargs)¶Invoke DELETE http request.
If api_base_url
configured, shortcut is available:
client.delete('posts/123')
authlib.flask.client.
OAuth
(app=None, cache=None, fetch_token=None, update_token=None)¶Registry for oauth clients.
Parameters: | app – the app instance of Flask |
---|
Create an instance with Flask:
oauth = OAuth(app, cache=cache)
You can also pass the instance of Flask later:
oauth = OAuth()
oauth.init_app(app, cache=cache)
Parameters: |
|
---|
init_app
(app, cache=None, fetch_token=None, update_token=None)¶Init app with Flask instance.
register
(name, overwrite=False, **kwargs)¶Registers a new remote application.
Parameters: |
|
---|
Find parameters from OAuthClient
.
When a remote app is registered, it can be accessed with
named attribute:
oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
authlib.flask.client.
RemoteApp
(name, fetch_token=None, update_token=None, fetch_request_token=None, save_request_token=None, **kwargs)¶Flask integrated RemoteApp of OAuthClient
.
It has built-in hooks for OAuthClient. The only required configuration
is token model.
Authorize access token.
Create a HTTP Redirect for Authorization Endpoint.
Parameters: |
|
---|---|
Returns: | A HTTP redirect response. |
Save redirect_uri
and state
into session during
authorize step.
authlib.django.client.
OAuth
(fetch_token=None)¶Registry for oauth clients.
Create an instance for registry:
oauth = OAuth()
register
(name, overwrite=False, **kwargs)¶Registers a new remote application.
Parameters: |
|
---|
Find parameters from OAuthClient
.
When a remote app is registered, it can be accessed with
named attribute:
oauth.register('twitter', client_id='', ...)
oauth.twitter.get('timeline')
authlib.django.client.
RemoteApp
(name, fetch_token=None, **kwargs)¶Django integrated RemoteApp of OAuthClient
.
It has built-in hooks for OAuthClient.
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. |
Save redirect_uri
and state
into session during
authorize step.
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
JWS.serialize_compact()
, build a JWS instance with JWA:
from authlib.jose import JWS
from authlib.jose import JWS_ALGORITHMS
jws = JWS(algorithms=JWS_ALGORITHMS)
# alg is a required parameter name
protected = {'alg': 'HS256'}
payload = b'example'
secret = b'secret'
jws.serialize_compact(protected, payload, secret)
There are other alg
that you could use. Here is a full list of available
algorithms:
For example, a JWS with RS256 requires a private PEM key to sign the JWS:
jws = JWS(algorithms=JWS_ALGORITHMS)
protected = {'alg': 'RS256'}
payload = b'example'
with open('private.pem', 'rb') as f:
secret = f.read()
jws.serialize_compact(protected, payload, secret)
To deserialize a JWS Compact Serialization, use
JWS.deserialize_compact()
:
# if it is a RS256, we use public RSA key
with open('public.pem', 'rb') as f:
key = f.read()
data = jws.deserialize_compact(s, key)
jws_header = data['header']
payload = data['payload']
A key
can be dynamically loaded, if you don’t know which key to be used:
def load_key(header, payload):
kid = header['kid']
return get_key_by_kid(kid)
jws.deserialize_compact(s, load_key)
The result of the deserialize_compact
is a dict, which contains header
and payload
. The value of the header
is a JWSHeader
.
Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).
JWS.serialize_json()
is used to generate a JWS JSON Serialization,
JWS.deserialize_json()
is used to extract a JWS JSON Serialization.
The usage is the same as “Compact Serialize and Deserialize”, the only
difference is the “header”:
# Flattened JSON serialization header syntax
header = {'protected': {'alg': 'HS256'}, 'header': {'cty': 'JWT'}}
key = b'secret'
payload = b'example'
jws.serialize_json(header, payload, key)
# General JSON serialization header syntax
header = [{'protected': {'alg': 'HS256'}, 'header': {'cty': 'JWT'}}]
jws.serialize_json(header, payload, key)
For general JSON Serialization, there may be many signatures, each signature can use its own key, in this case the dynamical key would be useful:
def load_private_key(header, payload):
kid = header['kid']
return get_private_key(kid)
header = [
{'protected': {'alg': 'HS256'}, 'header': {'kid': 'foo'}},
{'protected': {'alg': 'RS256'}, 'header': {'kid': 'bar'}},
]
data = jws.serialize_json(header, payload, load_private_key)
# data is a dict
def load_public_key(header, payload):
kid = header['kid']
return get_public_key(kid)
jws.deserialize_json(data, load_public_key)
Actually, there is a JWS.serialize()
and JWS.deserialize()
,
which can automatically serialize and deserialize Compact and JSON
Serializations.
The result of the deserialize_json
is a dict, which contains header
and payload
. The value of the header
is a JWSHeader
.
Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).
JWS
has a validation on header parameter names. It will first check
if the parameter name is in “Registered Header Parameter Names” defined by
RFC7515 Section 4.1. Then it will check if the parameter name is in your
defined private headers.
In this case, if there are header parameter names out of the registered header parameter names scope, you can pass the names:
private_headers = ['h1', 'h2']
jws = JWS(algorithms=JWS_ALGORITHMS, private_headers=private_headers)
JSON Web Encryption (JWE) 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
JWE.serialize_compact()
, build a JWE instance with JWA:
from authlib.jose import JWE
from authlib.jose import JWE_ALGORITHMS
jwe = JWE(algorithms=JWE_ALGORITHMS)
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
JWE.deserialize_compact()
:
with open('rsa_private.pem', 'rb') as f:
key = f.read()
data = jwe.deserialize_compact(s, key)
jwe_header = data['header']
payload = data['payload']
The result of the deserialize_compact
is a dict, which contains header
and payload
.
Using JWK for keys? Find how to use JWK with JSON Web Key (JWK).
A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. An example would help a lot:
{
"kty": "EC",
"crv": "P-256",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"kid": "iss-a"
}
This is an Elliptic Curve Public Key represented by JSON data structure. How
do we dumps
a key into JWK, and loads
JWK back into key? The interface
of JWK
contains these two methods.
Algorithms for kty
(Key Type) is defined by RFC7518: JSON Web Algorithms.
Available kty
values are: EC, RSA and oct. Initialize a JWK
instance with JWA:
from authlib.jose import JWK
from authlib.jose import JWK_ALGORITHMS
jwk = JWK(algorithms=JWK_ALGORITHMS)
key = read_file('public.pem')
obj = jwk.dumps(key, kty='RSA')
# obj is a dict, you may turn it into JSON
key = jwk.loads(obj)
There is an jwk
instance in authlib.jose
, so that you don’t need to
initialize JWK yourself, try:
from authlib.jose import jwk
key = read_file('public.pem')
obj = jwk.dumps(key, kty='RSA')
# obj is a dict, you may turn it into JSON
key = jwk.loads(obj)
You may pass extra parameters into dumps
method, available parameters can
be found on RFC7517 Section 4.
JSON Web Token (JWT) is structured by RFC7515: JSON Web Signature or :specs/rfc7516 with certain payload claims. The JWT implementation in Authlib has all built-in algorithms via RFC7518: JSON Web Algorithms, it can also load private/public keys of RFC7517: JSON Web Key:
>>> from authlib.jose import jwt
>>> header = {'alg': 'RS256'}
>>> payload = {'iss': 'Authlib', 'sub': '123', ...}
>>> key = read_file('private.pem')
>>> s = jwt.encode(header, payload, key)
>>> claims = jwt.decode(s, read_file('public.pem'))
>>> print(claims)
{'iss': 'Authlib', 'sub': '123', ...}
>>> print(claims.header)
{'alg': 'RS256', 'typ': 'JWT'}
>>> claims.validate()
The imported jwt
is an instance of JWT
. It has all supported JWS
algorithms, and it can handle JWK automatically. When JWT.encode()
a
payload, JWT will check payload claims for security, if you really want to
expose them, you can always turn it off via check=False
.
Important
JWT payload with JWS is not encrypted, it is just signed. Anyone can extract the payload without any private or public keys. Adding sensitive data like passwords, social security numbers in JWT payload is not safe if you are going to send them in a non-secure connection.
You can also use JWT with JWE which is encrypted. But this feature is not mature, documentation is not provided yet.
JWT.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.
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
Here are the details in documentation:
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.flask.oauth1.sqla import OAuth1ClientMixin
class Client(db.Model, OAuth1ClientMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
A client is registered by a user (developer) on your website. Get a deep
inside with ClientMixin
API reference.
A temporary credential is used to exchange a token credential. It is also known as “request token and secret”. Since it is temporary, it is better to save them into cache instead of database. A cache instance should has these methods:
.get(key)
.set(key, value, expires=None)
.delete(key)
A cache can be a memcache, redis or something else. If cache is not available, there is also a SQLAlchemy mixin:
from authlib.flask.oauth1.sqla import OAuth1TemporaryCredentialMixin
class TemporaryCredential(db.Model, OAuth1TemporaryCredentialMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
To make a Temporary Credentials model yourself, get more information with
ClientMixin
API reference.
A token credential is used to access resource owners’ resources. Unlike OAuth 2, the token credential will not expire in OAuth 1. This token credentials are supposed to be saved into a persist database rather than a cache.
Here is a SQLAlchemy mixin for easy integration:
from authlib.flask.oauth1.sqla import OAuth1TokenCredentialMixin
class TokenCredential(db.Model, OAuth1TokenCredentialMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
def set_user_id(self, user_id):
self.user_id = user_id
If SQLAlchemy is not what you want, read the API reference of
TokenCredentialMixin
and implement the missing
methods.
The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations. Authlib Flask integration has a built-in validation with cache.
If cache is not available, there is also a SQLAlchemy mixin:
from authlib.flask.oauth1.sqla import OAuth1TokenCredentialMixin
class TimestampNonce(db.Model, OAuth1TokenCredentialMixin)
id = db.Column(db.Integer, primary_key=True)
Authlib provides a ready to use AuthorizationServer
which has built-in tools to handle requests and responses:
from authlib.flask.oauth1 import AuthorizationServer
from authlib.flask.oauth1.sqla import create_query_client_func
query_client = create_query_client_func(db.session, Client)
server = AuthorizationServer(app, query_client=query_client)
It can also be initialized lazily with init_app:
server = AuthorizationServer()
server.init_app(app, query_client=query_client)
It is strongly suggested that you use a cache. In this way, you don’t have to re-implement a lot of the missing methods.
There are other configurations. It works well without any changes. Here is a list of them:
OAUTH1_TOKEN_GENERATOR | A string of module path for importing a
function to generate oauth_token |
OAUTH1_TOKEN_SECRET_GENERATOR | A string of module path for importing a
function to generate oauth_token_secret . |
OAUTH1_TOKEN_LENGTH | If OAUTH1_TOKEN_GENERATOR is not
configured, a random function will generate
the given length of oauth_token . Default
value is 42 . |
OAUTH1_TOKEN_SECRET_LENGTH | A random function will generate the given
length of oauth_token_secret . Default
value is 48 . |
These configurations are used to create the token_generator
function. But
you can pass the token_generator
when initializing the AuthorizationServer:
def token_generator():
return {
'oauth_token': random_string(20),
'oauth_token_secret': random_string(46)
}
server = AuthorizationServer(
app,
query_client=query_client,
token_generator=token_generator
)
There are missing hooks that should be register_hook
to AuthorizationServer.
There are helper functions for registering hooks. If cache is available, you
can take the advantage with:
from authlib.flask.oauth1.cache import (
register_nonce_hooks,
register_temporary_credential_hooks
)
from authlib.flask.oauth1.sqla import register_token_credential_hooks
register_nonce_hooks(server, cache)
register_temporary_credential_hooks(server, cache)
register_token_credential_hooks(server, db.session, TokenCredential)
If cache is not available, here are the helpers for SQLAlchemy:
from authlib.flask.oauth1.sqla import (
register_nonce_hooks,
register_temporary_credential_hooks,
register_token_credential_hooks
)
register_nonce_hooks(server, db.session, TimestampNonce)
register_temporary_credential_hooks(server, db.session, TemporaryCredential)
register_token_credential_hooks(server, db.session, TokenCredential)
It is ready to create the endpoints for authorization and issuing tokens. Let’s start with the temporary credentials endpoint, which is used for clients to fetch a temporary credential:
@app.route('/initiate', methods=['POST'])
def initiate_temporary_credential():
return server.create_temporary_credential_response()
The endpoint for resource owner authorization. OAuth 1 Client will redirect user to this authorization page, so that resource owner can grant or deny this request:
@app.route('/authorize', methods=['GET', 'POST'])
def authorize():
# make sure that user is logged in for yourself
if request.method == 'GET':
try:
req = server.check_authorization_request()
return render_template('authorize.html', req=req)
except OAuth1Error as error:
return render_template('error.html', error=error)
granted = request.form.get('granted')
if granted:
grant_user = current_user
else:
grant_user = None
try:
return server.create_authorization_response(grant_user)
except OAuth1Error as error:
return render_template('error.html', error=error)
Then the final token endpoint. OAuth 1 Client will use the given temporary
credential and the oauth_verifier
authorized by resource owner to exchange
the token credential:
@app.route('/token', methods=['POST'])
def issue_token():
return server.create_token_response()
Protect users resources, so that only the authorized clients with the authorized access token can access the given scope resources.
A resource server can be a different server other than the authorization server. Here is the way to protect your users’ resources:
from flask import jsonify
from authlib.flask.oauth1 import ResourceProtector, current_credential
from authlib.flask.oauth1.cache import create_exists_nonce_func
from authlib.flask.oauth1.sqla import (
create_query_client_func,
create_query_token_func
)
query_client = create_query_client_func(db.session, Client)
query_token = create_query_token_func(db.session, TokenCredential)
exists_nonce = create_exists_nonce_func(cache)
# OR: authlib.flask.oauth1.sqla.create_exists_nonce_func
require_oauth = ResourceProtector(
app, query_client=query_client,
query_token=query_token,
exists_nonce=exists_nonce,
)
# or initialize it lazily
require_oauth = ResourceProtector()
require_oauth.init_app(
app,
query_client=query_client,
query_token=query_token,
exists_nonce=exists_nonce,
)
@app.route('/user')
@require_oauth()
def user_profile():
user = current_credential.user
return jsonify(user)
The current_credential
is a proxy to the Token model you have defined above.
Since there is a user
relationship on the Token model, we can access this
user
with current_credential.user
.
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.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.flask.oauth1.
ResourceProtector
(app=None, query_client=None, query_token=None, exists_nonce=None)¶A protecting method for resource servers. Initialize a resource protector with the query_token method:
from authlib.flask.oauth1 import ResourceProtector, current_credential
from authlib.flask.oauth1.cache import create_exists_nonce_func
from authlib.flask.oauth1.sqla import (
create_query_client_func,
create_query_token_func,
)
from your_project.models import Token, User, cache
# you need to define a ``cache`` instance yourself
require_oauth= ResourceProtector(
app,
query_client=create_query_client_func(db.session, OAuth1Client),
query_token=create_query_token_func(db.session, OAuth1Token),
exists_nonce=create_exists_nonce_func(cache)
)
# or initialize it lazily
require_oauth = ResourceProtector()
require_oauth.init_app(
app,
query_client=create_query_client_func(db.session, OAuth1Client),
query_token=create_query_token_func(db.session, OAuth1Token),
exists_nonce=create_exists_nonce_func(cache)
)
get_client_by_id
(client_id)¶Get client instance with the given client_id
.
Parameters: | client_id – A string of client_id |
---|---|
Returns: | Client instance |
get_token_credential
(request)¶Fetch the token credential from data store like a database, framework should implement this function.
Parameters: | request – OAuth1Request instance |
---|---|
Returns: | Token model instance |
exists_nonce
(nonce, request)¶The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
Parameters: |
|
---|---|
Returns: | Boolean |
authlib.flask.oauth1.
current_credential
¶Routes protected by ResourceProtector
can access current credential
with this variable.
authlib.flask.oauth1.cache.
create_exists_nonce_func
(cache, key_prefix='nonce:', expires=86400)¶Create an exists_nonce
function that can be used in hooks and
resource protector.
Parameters: |
|
---|
authlib.flask.oauth1.cache.
register_nonce_hooks
(authorization_server, cache, key_prefix='nonce:', expires=86400)¶Register nonce related hooks to authorization server.
Parameters: |
|
---|
authlib.flask.oauth1.cache.
register_temporary_credential_hooks
(authorization_server, cache, key_prefix='temporary_credential:')¶Register temporary credential related hooks to authorization server.
Parameters: |
|
---|
authlib.flask.oauth1.sqla.
create_query_client_func
(session, model_class)¶Create an query_client
function that can be used in authorization
server and resource protector.
Parameters: |
|
---|
authlib.flask.oauth1.sqla.
create_query_token_func
(session, model_class)¶Create an query_token
function that can be used in
resource protector.
Parameters: |
|
---|
authlib.flask.oauth1.sqla.
create_exists_nonce_func
(session, model_class)¶Create an exists_nonce
function that can be used in hooks and
resource protector.
Parameters: |
|
---|
authlib.flask.oauth1.sqla.
register_nonce_hooks
(authorization_server, session, model_class)¶Register nonce related hooks to authorization server.
Parameters: |
|
---|
authlib.flask.oauth1.sqla.
register_temporary_credential_hooks
(authorization_server, session, model_class)¶Register temporary credential related hooks to authorization server.
Parameters: |
|
---|
authlib.flask.oauth1.sqla.
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 server in Flask. Instead, we will learn how the Flask implementation works, and some technical details in an OAuth 2.0 provider.
If you need a quick example, here are the official tutorial guide and examples on GitHub:
At the very beginning, we need to have some basic understanding of the OAuth 2.0.
Important
If you are developing on your localhost, remember to set the environment variable:
export AUTHLIB_INSECURE_TRANSPORT=true
Looking for OAuth 2 client? Check out Flask OAuth Client.
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.flask.oauth2.sqla import OAuth2ClientMixin
class Client(Model, OAuth2ClientMixin):
id = Column(Integer, primary_key=True)
user_id = Column(
Integer, ForeignKey('user.id', ondelete='CASCADE')
)
user = relationship('User')
A client is registered by a user (developer) on your website. If you decide to
implement all the missing methods by yourself, get a deep inside with
ClientMixin
API reference.
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.flask.oauth2.sqla import OAuth2TokenMixin
class Token(db.Model, OAuth2TokenMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
A token is associated with a resource owner. There is no certain name for
it, here we call it user
, but it can be anything else.
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.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.flask.oauth2.sqla import (
create_query_client_func,
create_save_token_func
)
query_client = create_query_client_func(db.session, Client)
save_token = create_save_token_func(db.session, Token)
server = AuthorizationServer(
app, query_client=query_client, save_token=save_token
)
It can also be initialized lazily with init_app:
server = AuthorizationServer()
server.init_app(app, query_client=query_client, save_token=save_token)
It works well without configuration. However, it can be configured with these settings:
OAUTH2_TOKEN_EXPIRES_IN | A dict to define expires_in for each grant |
OAUTH2_ACCESS_TOKEN_GENERATOR | A function or string of module path for importing
a function to generate access_token |
OAUTH2_REFRESH_TOKEN_GENERATOR | A function or string of module path for importing
a function to generate refresh_token . It can
also be True/False |
OAUTH2_ERROR_URIS | A list of tuple for (error , error_uri ) |
Hint
Here is an example of OAUTH2_TOKEN_EXPIRES_IN
:
OAUTH2_TOKEN_EXPIRES_IN = {
'authorization_code': 864000,
'implicit': 3600,
'password': 864000,
'client_credentials': 864000
}
Here is an example of OAUTH2_ACCESS_TOKEN_GENERATOR
:
def gen_access_token(client, grant_type, user, scope):
return create_some_random_string()
OAUTH2_REFRESH_TOKEN_GENERATOR
accepts the same parameters.
Now define an endpoint for authorization. This endpoint is used by
authorization_code
and implicit
grants:
from flask import request, render_template
from your_project.auth import current_user
@app.route('/oauth/authorize', methods=['GET', 'POST'])
def authorize():
# Login is required since we need to know the current resource owner.
# It can be done with a redirection to the login page, or a login
# form on this authorization page.
if request.method == 'GET':
grant = server.validate_consent_request(end_user=current_user)
return render_template(
'authorize.html',
grant=grant,
user=current_user,
)
confirmed = request.form['confirm']
if confirmed:
# granted by resource owner
return server.create_authorization_response(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.flask.oauth2.sqla import OAuth2AuthorizationCodeMixin
class AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
Implement this grant by subclass AuthorizationCodeGrant
:
from authlib.oauth2.rfc6749 import grants
from authlib.common.security import generate_token
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def create_authorization_code(self, client, grant_user, request):
# you can use other method to generate this code
code = generate_token(48)
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=grant_user.get_user_id(),
)
db.session.add(item)
db.session.commit()
return code
def parse_authorization_code(self, code, client):
item = AuthorizationCode.query.filter_by(
code=code, client_id=client.client_id).first()
if item and not item.is_expired():
return item
def delete_authorization_code(self, authorization_code):
db.session.delete(authorization_code)
db.session.commit()
def authenticate_user(self, authorization_code):
return User.query.get(authorization_code.user_id)
# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant)
Note
AuthorizationCodeGrant is the most complex grant.
Default allowed Client Authentication Methods are:
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.flask.oauth2 import ResourceProtector, current_token
from authlib.oauth2.rfc6750 import BearerTokenValidator
class MyBearerTokenValidator(BearerTokenValidator):
def authenticate_token(self, token_string):
return Token.query.filter_by(access_token=token_string).first()
def request_invalid(self, request):
return False
def token_revoked(self, token):
return token.revoked
require_oauth = ResourceProtector()
# only bearer token is supported currently
require_oauth.register_token_validator(MyBearerTokenValidator())
# you can also create BearerTokenValidator with shortcut
from authlib.flask.oauth2.sqla import create_bearer_token_validator
BearerTokenValidator = create_bearer_token_validator(db.session, Token)
require_oauth.register_token_validator(BearerTokenValidator())
@app.route('/user')
@require_oauth('profile')
def user_profile():
user = current_token.user
return jsonify(user)
If the resource is not protected by a scope, use None
:
@app.route('/user')
@require_oauth()
def user_profile():
user = current_token.user
return jsonify(user)
# or with None
@app.route('/user')
@require_oauth(None)
def user_profile():
user = current_token.user
return jsonify(user)
The current_token
is a proxy to the Token model you have defined above.
Since there is a user
relationship on the Token model, we can access this
user
with current_token.user
.
If decorator is not your favorite, there is a with
statement for you:
@app.route('/user')
def user_profile():
with require_oauth.acquire('profile') as token:
user = token.user
return jsonify(user)
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)
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.
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. Basically, all you have to do is to extend
OIDCAuthorizationCodeMixin
instead of AuthorizationCodeMixin
in your AuthorizationCode
class. This will add a nonce
attribute
and its getter to it which will be required in this new code flow:
from authlib.flask.oauth2.sqla import OIDCAuthorizationCodeMixin
class AuthorizationCode(db.Model, OIDCAuthorizationCodeMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')
)
user = db.relationship('User')
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 create_authorization_code
method:
class AuthorizationCodeGrant(_AuthorizationCodeGrant):
def create_authorization_code(self, client, grant_user, request):
code = generate_token(48)
# openid request MAY have "nonce" parameter
nonce = request.data.get('nonce')
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=grant_user.id,
nonce=nonce,
)
db.session.add(item)
db.session.commit()
return code
# ...
Finally, you can register AuthorizationCodeGrant
with OpenIDCode
extension:
# register it to grant endpoint
server.register_grant(OpenIDCodeGrant, [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 create_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 create_authorization_code(self, client, grant_user, request):
code = generate_token(48)
nonce = request.data.get('nonce')
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=grant_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.flask.oauth2.
AuthorizationServer
(app=None, query_client=None, save_token=None)¶Flask implementation of authlib.rfc6749.AuthorizationServer
.
Initialize it with query_client
, save_token
methods and Flask
app instance:
def query_client(client_id):
return Client.query.filter_by(client_id=client_id).first()
def save_token(token, request):
if request.user:
user_id = request.user.get_user_id()
else:
user_id = None
client = request.client
tok = Token(
client_id=client.client_id,
user_id=user.get_user_id(),
**token
)
db.session.add(tok)
db.session.commit()
server = AuthorizationServer(app, query_client, save_token)
# or initialize lazily
server = AuthorizationServer()
server.init_app(app, query_client, save_token)
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 token endpoint to authorization server. e.g. RevocationEndpoint:
authorization_server.register_endpoint(RevocationEndpoint)
Parameters: | endpoint_cls – A token endpoint class |
---|
register_grant
(grant_cls, extensions=None)¶Register a grant class into the endpoint registry. Developers
can implement the grants in authlib.oauth2.rfc6749.grants
and
register with this method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def authenticate_user(self, credential):
# ...
authorization_server.register_grant(AuthorizationCodeGrant)
Parameters: |
|
---|
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.flask.oauth2.
ResourceProtector
¶A protecting method for resource servers. Creating a require_oauth
decorator easily with ResourceProtector:
from authlib.flask.oauth2 import ResourceProtector
require_oauth = ResourceProtector()
# add bearer token validator
from authlib.oauth2.rfc6750 import BearerTokenValidator
from project.models import Token
class MyBearerTokenValidator(BearerTokenValidator):
def authenticate_token(self, token_string):
return Token.query.filter_by(access_token=token_string).first()
def request_invalid(self, request):
return False
def token_revoked(self, token):
return False
require_oauth.register_token_validator(MyBearerTokenValidator())
# protect resource with require_oauth
@app.route('/user')
@require_oauth('profile')
def user_profile():
user = User.query.get(current_token.user_id)
return jsonify(user.to_dict())
raise_error_response
(error)¶Raise HTTPException for OAuth2Error. Developers can re-implement this method to customize the error response.
Parameters: | error – OAuth2Error |
---|---|
Raise: | HTTPException |
acquire_token
(scope=None, operator='AND')¶A method to acquire current valid token with the given scope.
Parameters: |
|
---|---|
Returns: | token object |
acquire
(scope=None, operator='AND')¶The with statement of require_oauth
. Instead of using a
decorator, you can use a with statement instead:
@app.route('/api/user')
def user_api():
with require_oauth.acquire('profile') as token:
user = User.query.get(token.user_id)
return jsonify(user.to_dict())
authlib.flask.oauth2.
current_token
¶Routes protected by ResourceProtector
can access current token
with this variable:
from authlib.flask.oauth2 import current_token
@require_oauth()
@app.route('/user_id')
def user_id():
# current token instance of the OAuth Token model
return current_token.user_id
authlib.flask.oauth2.
client_authenticated
¶Signal when client is authenticated
authlib.flask.oauth2.
token_revoked
¶Signal when token is revoked
authlib.flask.oauth2.
token_authenticated
¶Signal when token is authenticated
Use cache for authorization code grant endpoint.
Parameters: |
|
---|
authlib.flask.oauth2.sqla.
create_query_client_func
(session, client_model)¶Create an query_client
function that can be used in authorization
server.
Parameters: |
|
---|
authlib.flask.oauth2.sqla.
create_save_token_func
(session, token_model)¶Create an save_token
function that can be used in authorization
server.
Parameters: |
|
---|
authlib.flask.oauth2.sqla.
create_query_token_func
(session, token_model)¶Create an query_token
function for revocation, introspection
token endpoints.
Parameters: |
|
---|
authlib.flask.oauth2.sqla.
create_revocation_endpoint
(session, token_model)¶Create a revocation endpoint class with SQLAlchemy session and token model.
Parameters: |
|
---|
authlib.flask.oauth2.sqla.
create_bearer_token_validator
(session, token_model)¶Create an bearer token validator class with SQLAlchemy session and token model.
Parameters: |
|
---|
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
Here are the details in documentation:
The Authorization Server provides several endpoints for temporary credentials, authorization, and issuing token credentials. When the resource owner (user) grants the authorization, this server will issue a token credential to the client.
Currently, Authlib Django implementation is using cache a lot, which means you don’t have to handle temporary credentials, timestamp and nonce yourself, they are all built-in.
To create an authorization server, only Client and Token models are required:
from your_project.models import Client, Token
from authlib.django.oauth1 import CacheAuthorizationServer
authorization_server = CacheAuthorizationServer(Client, Token)
Resource Owner 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.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.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.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 OAuth 2 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.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():
return server.create_token_response()
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 create_authorization_code(self, client, grant_user, request):
code = generate_token(48)
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
response_type=request.response_type,
scope=request.scope,
user=grant_user,
)
item.save()
return code
def parse_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.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.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
@app.route('/user')
@require_oauth(None)
def user_profile():
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))
OpenID Connect 1.0 are built custom grant types and grant extensions. You need to read the Authorization Server chapter at first.
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.create_authorization_code
method:
class AuthorizationCodeGrant(_AuthorizationCodeGrant):
def create_authorization_code(self, client, grant_user, request):
code = generate_token(48)
# openid request MAY have "nonce" parameter
nonce = request.data.get('nonce')
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user=grant_user,
nonce=nonce,
)
item.save()
return code
Finally, you can register AuthorizationCodeGrant
with OpenIDCode
extension:
# register it to grant endpoint
server.register_grant(OpenIDCodeGrant, [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 create_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 create_authorization_code(self, client, grant_user, request):
code = generate_token(48)
# openid request MAY have "nonce" parameter
nonce = request.data.get('nonce')
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user=grant_user,
nonce=nonce,
)
item.save()
return 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.django.oauth2.
AuthorizationServer
(client_model, token_model, generate_token=None, metadata=None)¶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 token endpoint to authorization server. e.g. RevocationEndpoint:
authorization_server.register_endpoint(RevocationEndpoint)
Parameters: | endpoint_cls – A token endpoint class |
---|
register_grant
(grant_cls, extensions=None)¶Register a grant class into the endpoint registry. Developers
can implement the grants in authlib.oauth2.rfc6749.grants
and
register with this method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def authenticate_user(self, credential):
# ...
authorization_server.register_grant(AuthorizationCodeGrant)
Parameters: |
|
---|
authlib.django.oauth2.
ResourceProtector
¶return_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
(request, scope=None, operator='AND')¶A method to acquire current valid token with the given scope.
Parameters: |
|
---|---|
Returns: | token object |
authlib.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 TokenRequest |
---|---|
Returns: | Boolean |
token_revoked
(token)¶Check if this token is revoked. Developers MUST re-implement this
method. If there is a column called revoked
on the token table:
def token_revoked(self, token):
return token.revoked
Parameters: | token – token instance |
---|---|
Returns: | Boolean |
authlib.django.oauth2.
RevocationEndpoint
(request, 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.django.oauth2.
client_authenticated
¶Signal when client is authenticated
authlib.django.oauth2.
token_revoked
¶Signal when token is revoked
authlib.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 Understand 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, generate_token, save_token, metadata=None)¶Authorization server that handles Authorization Endpoint and Token Endpoint.
Parameters: |
|
---|
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_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 token endpoint to authorization server. e.g. RevocationEndpoint:
authorization_server.register_endpoint(RevocationEndpoint)
Parameters: | endpoint_cls – A token endpoint class |
---|
register_grant
(grant_cls, extensions=None)¶Register a grant class into the endpoint registry. Developers
can implement the grants in authlib.oauth2.rfc6749.grants
and
register with this method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def authenticate_user(self, credential):
# ...
authorization_server.register_grant(AuthorizationCodeGrant)
Parameters: |
|
---|
send_signal
(name, *args, **kwargs)¶Framework integration can re-implement this method to support signal system.
validate_requested_scope
(request)¶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_client_type
(client_type)¶Validate if the client is the given client_type
. The available
choices are:
Developers can overwrite this method to implement a new logic.
Parameters: | client_type – string of “public” or “confidential” |
---|---|
Returns: | bool |
check_grant_type
(grant_type)¶Validate if the client can handle the given grant_type. There are four grant types defined by RFC6749:
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)¶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)¶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)¶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)¶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)¶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)¶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)¶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)¶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', '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.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) |
---|
Save authorization_code for later use. Developers should implement it in subclass. Here is an example:
from authlib.common.security import generate_token
def create_authorization_code(self, client, request):
code = generate_token(48)
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=grant_user.get_user_id(),
)
item.save()
return code
Parameters: |
|
---|---|
Returns: | code string |
Get authorization_code from previously savings. Developers should implement it in subclass:
def parse_authorization_code(self, code, client):
return Authorization.get(code=code, client_id=client.client_id)
Parameters: |
|
---|---|
Returns: | authorization_code object |
Delete authorization code from database or cache. Developers should implement it in subclass, e.g.:
def delete_authorization_code(self, authorization_code):
authorization_code.delete()
Parameters: | authorization_code – the instance of authorization_code |
---|
authenticate_user
(authorization_code)¶Authenticate the user related to this authorization_code. Developers should implement this method in subclass, e.g.:
def authenticate_user(self, authorization_code):
return User.query.get(authorization_code.user_id)
Parameters: | authorization_code – AuthorizationCode object |
---|---|
Returns: | user |
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 TokenRequest |
---|---|
Returns: | Boolean |
token_revoked
(token)¶Check if this token is revoked. Developers MUST re-implement this
method. If there is a column called revoked
on the token table:
def token_revoked(self, token):
return token.revoked
Parameters: | token – token instance |
---|---|
Returns: | Boolean |
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. But there are missing methods to be implemented:
from authlib.oauth2.rfc7009 import RevocationEndpoint
class MyRevocationEndpoint(RevocationEndpoint):
def query_token(self, token, token_type_hint, client):
q = Token.query.filter_by(client_id=client.client_id)
if token_type_hint == 'access_token':
return q.filter_by(access_token=token).first()
elif token_type_hint == 'refresh_token':
return q.filter_by(refresh_token=token).first()
# without token_type_hint
item = q.filter_by(access_token=token).first()
if item:
return item
return q.filter_by(refresh_token=token).first()
def revoke_token(self, token):
token.revoked = True
db.session.add(token)
db.session.commit()
# register it to authorization server
server.register_endpoint(MyRevocationEndpoint)
There is also a shortcut method to create revocation endpoint:
from authlib.flask.oauth2.sqla import create_revocation_endpoint
RevocationEndpoint = create_revocation_endpoint(db.session, Token)
# register it to authorization server
server.register_endpoint(RevocationEndpoint)
After the registration, you can create a response with:
@app.route('/oauth/revoke', methods=['POST'])
def revoke_token():
return server.create_endpoint_response(MyRevocationEndpoint.ENDPOINT_NAME)
authlib.oauth2.rfc7009.
RevocationEndpoint
(request, server)¶Implementation of revocation endpoint which is described in RFC7009.
ENDPOINT_NAME
= 'revocation'¶Endpoint name to be registered
validate_endpoint_request
()¶The client constructs the request by including the following parameters using the “application/x-www-form-urlencoded” format in the HTTP request entity-body:
create_endpoint_response
()¶Validate revocation request and create the response for revocation. For example, a client may request the revocation of a refresh token with the following request:
POST /revoke HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
Returns: | (status_code, body, headers) |
---|
query_token
(token, token_type_hint, client)¶Get the token from database/storage by the given token string. Developers should implement this method:
def query_token(self, token, token_type_hint, client):
if token_type_hint == 'access_token':
return Token.query_by_access_token(token, client.client_id)
if token_type_hint == 'refresh_token':
return Token.query_by_refresh_token(token, client.client_id)
return Token.query_by_access_token(token, client.client_id) or Token.query_by_refresh_token(token, client.client_id)
revoke_token
(token)¶Mark token as revoked. Since token MUST be unique, it would be dangerous to delete it. Consider this situation:
It would be secure to mark a token as revoked:
def revoke_token(self, token):
token.revoked = True
token.save()
authenticate_endpoint_client
()¶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.rfc7515.
JWS
¶alias of authlib.jose.rfc7515.jws.JsonWebSignature
authlib.jose.rfc7515.
JWSHeader
(protected, header)¶Header object for JWS. It combine the protected header and unprotected header together. JWSHeader itself is a dict of the combined dict. e.g.
>>> protected = {'alg': 'HS256'}
>>> header = {'kid': 'a'}
>>> jws_header = JWSHeader(protected, header)
>>> print(jws_header)
{'alg': 'HS256', 'kid': 'a'}
>>> jws_header.protected == protected
>>> jws_header.header == header
Parameters: |
|
---|
authlib.jose.rfc7515.
JWSObject
(header, payload, type='compact')¶A dict instance to represent a JWS object.
authlib.jose.rfc7515.
JWSAlgorithm
¶Interface for JWS algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWS with this base implementation.
prepare_private_key
(key)¶Prepare key for sign signature.
prepare_public_key
(key)¶Prepare key for verify signature.
sign
(msg, key)¶Sign the text msg with a private/sign key.
Parameters: |
|
---|---|
Returns: | bytes |
verify
(msg, key, sig)¶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.rfc7516.
JWE
¶alias of authlib.jose.rfc7516.jwe.JsonWebEncryption
authlib.jose.rfc7516.
JWEAlgorithm
¶Interface for JWE algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWE with this base implementation.
authlib.jose.rfc7516.
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.rfc7516.
JWEZipAlgorithm
¶This section contains the generic implementation of RFC7517. Find how to use it in JWK Guide.
authlib.jose.rfc7517.
JWK
¶alias of authlib.jose.rfc7517.jwk.JsonWebKey
authlib.jose.rfc7517.
JWKAlgorithm
¶name
= None¶Interface for JWK algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWK with this base implementation.
prepare_key
(key)¶Prepare key before dumping it into JWK.
loads
(obj)¶Load JWK dict object into a public/private key.
dumps
(key)¶Dump a public/private key into JWK dict object.
This section contains the generic implementation of RFC7518.
The interface for JWS Algorithms are all inherit from
authlib.jose.rfc7515.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.rfc7516.JWEAlgorithm
. For enc
, the interface are
inherited from authlib.jose.rfc7516.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. The interface for JWK Algorithms are all inherited from
authlib.jose.rfc7517.JWKAlgorithm
. The available values of kty
:
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.rfc7519.
JWT
¶alias of authlib.jose.rfc7519.jwt.JsonWebToken
authlib.jose.rfc7519.
JWTClaims
(payload, header, options=None, params=None)¶Payload claims for JWT, which contains a validate interface.
Parameters: |
|
---|
An example on options
parameter, the format is inspired by
OpenID Connect Claims:
{
"iss": {
"essential": True,
"values": ["https://example.com", "https://example.org"]
},
"sub": {
"essential": True
"value": "248289761001"
},
"jti": {
"validate": validate_jti
}
}
validate
(now=None, leeway=0)¶Validate everything in claims payload.
validate_iss
()¶The “iss” (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The “iss” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
validate_sub
()¶The “sub” (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The “sub” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
validate_aud
()¶The “aud” (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the “aud” claim when this claim is present, then the JWT MUST be rejected. In the general case, the “aud” value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the “aud” value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
validate_exp
(now, leeway)¶The “exp” (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the “exp” claim requires that the current date/time MUST be before the expiration date/time listed in the “exp” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
validate_nbf
(now, leeway)¶The “nbf” (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the “nbf” claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the “nbf” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
validate_iat
(now, leeway)¶The “iat” (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
validate_jti
()¶The “jti” (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The “jti” claim can be used to prevent the JWT from being replayed. The “jti” value is a case- sensitive string. Use of this claim is OPTIONAL.
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, 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
.
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 has provided you a helper function to register client assertion
authentication method easily to OAuth2Session
. Take client_secret_jwt
as an example:
from authlib.oauth2.rfc7523 import register_session_client_auth_method
from authlib.client import OAuth2Session
session = OAuth2Session(
'your-client-id', 'your-client-secret',
token_endpoint_auth_method='client_secret_jwt'
)
# just one hook here
register_session_client_auth_method(session)
session.fetch_access_token('https://example.com/oauth/token')
How about private_key_jwt
? It is the same as client_secret_jwt
:
with open('your-private-key.pem', 'rb') as f:
private_key = f.read()
session = OAuth2Session(
'your-client-id', private_key,
token_endpoint_auth_method='private_key_jwt' # NOTICE HERE
)
register_session_client_auth_method(session)
session.fetch_access_token('https://example.com/oauth/token')
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
(claims)¶Authenticate user with the given assertion claims. Developers MUST implement it in subclass, e.g.:
def authenticate_user(self, claims):
return User.get_by_sub(claims['sub'])
Parameters: | claims – assertion payload claims |
---|---|
Returns: | User instance |
authenticate_client
(claims)¶Authenticate client with the given assertion claims. Developers MUST implement it in subclass, e.g.:
def authenticate_client(self, claims):
return Client.get_by_iss(claims['iss'])
Parameters: | claims – assertion payload claims |
---|---|
Returns: | Client instance |
resolve_public_key
(headers, payload)¶Find public key to verify assertion signature. Developers MUST implement it in subclass, e.g.:
def resolve_public_key(self, headers, payload):
jwk_set = get_jwk_set_by_iss(payload['iss'])
return filter_jwk_set(jwk_set, headers['kid'])
Parameters: |
|
---|---|
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.
register_session_client_auth_method
(session, token_url=None, **kwargs)¶Register “client_secret_jwt” or “private_key_jwt” token endpoint auth method to OAuth2Session.
Parameters: |
|
---|
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.
New in version v0.10.
In order to apply proof key for code exchange, you need to register the
CodeChallenge
extension to AuthorizationCodeGrant
. But before
that, we need to re-design our AuthorizationCode database. If you are using
Flask, check the section Authorization Code Grant.
The new database SHOULD contain two more columns:
And the AuthorizationCodeGrant
should record the code_challenge
and
code_challenge_method
into database in create_authorization_code
method:
class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
def create_authorization_code(self, client, grant_user, request):
code = generate_token(48)
# NOTICE BELOW
code_challenge = request.data.get('code_challenge')
code_challenge_method = request.data.get('code_challenge_method')
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
user_id=grant_user.get_user_id(),
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
)
db.session.add(item)
db.session.commit()
return code
Now you can register your AuthorizationCodeGrant
with the extension:
from authlib.oauth2.rfc7636 import CodeChallenge
server.register_grant(MyAuthorizationCodeGrant, [CodeChallenge(required=True)])
If required=True
, code challenge is required for authorization code flow.
If required=False
, it is optional, it will only valid the code challenge
when clients send these parameters.
code_challenge
in Client¶Read the Code Challenge section in the framework integrations:
It is also possible to add code_challenge
in OAuth2Session
,
consider that we already have a session
:
>>> from authlib.oauth2.rfc7636 import create_s256_code_challenge
>>> code_verifier = 'generate-a-urlsafe-string'
>>> code_challenge = create_s256_code_challenge(code_verifier)
>>> uri, state = session.authorization_url(authorize_url, code_challenge=code_challenge, code_challenge_method='S256')
>>> # visit uri, get the response
>>> authorization_response = 'https://example.com/auth?code=42..e9&state=d..t'
>>> token = session.fetch_access_token(access_token_url, authorization_response=authorization_response, code_verifier=code_verifier)
The authorization flow is the same as in OAuth 2 Session, what you need to do is:
code_challenge
and code_challenge_method
in authorization_url()
.code_verifier
in fetch_access_token()
.authlib.oauth2.rfc7636.
CodeChallenge
(required=False)¶CodeChallenge extension to Authorization Code Grant. It is used to improve the security of Authorization Code flow for public clients by sending extra “code_challenge” and “code_verifier” to the authorization server.
The AuthorizationCodeGrant SHOULD save the code_challenge and code_challenge_method into database when create_authorization_code. Then register this extension via:
server.register_grant(
AuthorizationCodeGrant,
[CodeChallenge(required=True)]
)
DEFAULT_CODE_CHALLENGE_METHOD
= 'plain'¶defaults to “plain” if not present in the request
SUPPORTED_CODE_CHALLENGE_METHOD
= ['plain', 'S256']¶supported code_challenge_method
Get “code_challenge” associated with this authorization code. Developers CAN re-implement it in subclass, the default logic:
def get_authorization_code_challenge(self, authorization_code):
return authorization_code.code_challenge
Parameters: | authorization_code – the instance of authorization_code |
---|
Get “code_challenge_method” associated with this authorization code. Developers CAN re-implement it in subclass, the default logic:
def get_authorization_code_challenge_method(self, authorization_code):
return authorization_code.code_challenge_method
Parameters: | authorization_code – the instance of authorization_code |
---|
This section contains the generic implementation of RFC7662.
With the help of register_endpoint
offered by Flask OAuth 2.0 Server,
we can easily add introspection endpoint to the authorization server. But
first, we need to implement the missing methods:
from authlib.oauth2.rfc7662 import IntrospectionEndpoint
class MyIntrospectionEndpoint(IntrospectionEndpoint):
def query_token(self, token, token_type_hint, client):
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
(request, server)¶Implementation of introspection endpoint which is described in RFC7662.
ENDPOINT_NAME
= 'introspection'¶Endpoint name to be registered
validate_endpoint_request
()¶The protected resource calls the introspection endpoint using an HTTP
POST
request with parameters sent as
“application/x-www-form-urlencoded” data. The protected resource sends a
parameter representing the token along with optional parameters
representing additional context that is known by the protected resource
to aid the authorization server in its response.
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
()¶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
()¶Authentication client for endpoint with CLIENT_AUTH_METHODS
.
This part of the documentation covers the specification of OpenID Connect. Learn how to use it in Flask OpenID Connect 1.0.
authlib.oidc.core.grants.
OpenIDCode
(key=None, alg=None, iss=None, exp=None, exists_nonce=None, required_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_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_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
Save authorization_code for later use. Developers should implement it in subclass. Here is an example:
from authlib.common.security import generate_token
def create_authorization_code(self, client, request):
code = generate_token(48)
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
scope=request.scope,
nonce=request.data.get('nonce'),
user_id=grant_user.get_user_id(),
)
item.save()
return code
Parameters: |
|
---|---|
Returns: | code string |
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 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.
AssertionSession
.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 Patreon. You are welcome to become a supporter, a backer or a company sponsor.
Authlib is licensed under BSD, if this license doesn’t fit your company, consider to purchase a commercial license.
Find more information on https://authlib.org/support#commercial-license
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 a 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 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
.
Released on Oct 12, 2018.
The most important change in this version is grant extension system. When registering a grant, developers can pass extensions to the grant:
authorization_server.register_grant(GrantClass, [extension])
Find Flask Grant Extensions implementation.
RFC implementations and updates in this release:
Besides that, there are other improvements:
save_authorize_state
method on Flask and Django clientfetch_token
to Django OAuth client@require_oauth
Multiple ScopesDeprecate Changes: find how to solve the deprecate issues via https://git.io/fAmW1
Released on Aug 12, 2018. Fun Dive.
There is no big break changes in this version. The very great improvement is adding JWE support. But the JWA parts of JWE are not finished yet, use with caution.
RFC implementations in this release:
Other Changes:
authlib.client.apps
from v0.7 has been dropped.Released on Jun 17, 2018. Try Django.
Authlib has tried to introduce Django OAuth server implementation in this version. It turns out that it is not that easy. In this version, only Django OAuth 1.0 server is provided.
As always, there are also RFC features added in this release, here is what’s in version 0.8:
response_mode=form_post
support for OpenID Connect.Improvement in this release:
AuthlibBaseError
.authlib.flask.oauth2.sqla
via issue#57.require_oauth.acquire
with statement, get example on Flask OAuth 2.0 Server.Deprecate Changes: find how to solve the deprecate issues via https://git.io/vhL75
OAUTH2_EXPIRES_IN
to OAUTH2_TOKEN_EXPIRES_IN
.create_expires_generator
to
create_token_expires_in_generator
Released on Apr 28, 2018. Better Beta.
Authlib has changed its license from LGPL to AGPL. This is not a huge release like v0.6, but it still contains some deprecate changes, the good news is they are compatible, they won’t break your project. Authlib can’t go further without these deprecate changes.
As always, Authlib is adding specification implementations. Here is what’s in version 0.7:
JWS
, make it a full implementation.AssertionSession
, only works with RFC7523.JWTBearerGrant
, read the guide in
RFC7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants.Besides that, there are more changes:
overwrite
parameter for framework integrations clients.response_mode=query
for OpenID Connect implicit and hybrid flow.authlib.client.apps
. Use Loginpass instead.Deprecate Changes: find how to solve the deprecate issues via https://git.io/vpCH5
Released on Mar 20, 2018. Going Beta!
From alpha to beta. This is a huge release with lots of deprecating changes and some breaking changes. And finally, OpenID Connect server is supported by now, because Authlib has added these specifications:
The specifications are not completed yet, but they are ready to use. The missing RFC7516 (JWE) is going to be implemented in next version. Open ID Connect 1.0 is added with:
Besides that, there are more changes:
token_endpoint_auth_method
concept defined in RFC7591.Breaking Changes:
authlib.flask.oauth2.sqla
has been changed a lot.
If you are using it, you need to upgrade your database.register_token_validator
on
ResourceProtector.authlib.client.oauth1.OAuth1
has been renamed to
authlib.client.oauth1.OAuth1Auth
.Deprecate Changes: find how to solve the deprecate issues via https://git.io/vAAUK
Find old changelog at https://github.com/lepture/authlib/releases
Consider to follow Authlib on Twitter, and subscribe Authlib Blog.