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.