Easy steps to set up your backend API token using Django OAuth Toolkit
After finishing building our React frontend and Django backend, there's one more thing we need to worry about. If we host our backend on AWS API Gateway, we don't want everyone having permission to call our backend API. I only want our frontend app to access my backend API. There're a couple of options here. One is securing my API directly on AWS API Gateway. The second option is to use Django OAuth Toolkit, which is what this blog post is about.
Django OAuth Toolkit
The documentation of Django OAuth Toolkit is quite in depth. But I found it a bit lack of details and hard to get started with for starters. So I summarised a few steps below which I think is much easier to get started with or at least for me it is easier.
Python code setup
- Install the package
pip install django-oauth-toolkit
- In settings.py in Django, add below line in MIDDLEWARE
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# below is what I added
'oauth2_provider.middleware.OAuth2TokenMiddleware',
]
Add oauth2provider app in INSTALLEDAPPS in settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'oauth2_provider', # this is the line I added for oauth toolkit
'rest_framework',
'django_filters',
'api',
]
Add below lines at the bottom of settings.py
AUTHENTICATION_BACKENDS = (
'oauth2_provider.backends.OAuth2Backend',
'django.contrib.auth.backends.ModelBackend',
)
OAUTH2_PROVIDER = {
'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.JSONOAuthLibCore',
'SCOPES': {
'read': 'Read scope',
'write': 'Write scope',
'groups': 'Access to your groups',
},
}
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
}
- In url.py , add below route :
path('oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),
- Open above url in browser to register OAuth2 Client Application. For a front end app like React, we just need to register a "Public" Client Type app with "Client Credential" Authorization Grant Type as our client id and client secret will be stored in front end javascript. People can just click inspect element to find your client secret. So we need to set it as Public here according to the protocol. Then we can just leave the redirect url blank. Name the application anything that makes sense to you. I named it react for example. Remember to keep the client id and client secret saved in somewhere you can find later. Save the app.
401 Not Authorized error at this point
At this point, if I run my React app, we get 401 not authorized error. That's because we need to send the client id and client secret to /oauth/token/ to exchange for access token. And then we need to use send this access token along with the requests made to our api.
The scope of my access tokens is read scope here as I assign 'read' scope in the javascript object when I send requests to '/oauth/token/'. You can also assign other scope e.g. 'write' or 'read write' in one string (note: it can't be in an array) to the scope property of the javascript object.
- Exchange for access token with the client id and client secret. I did something like below :
Axios.post('/oauth/token/', data).then((response) => {
_accessToken = response.data.access_token;
resolve(_accessToken);
});
Send this access token along with the get or post request to our backend api, for example
Axios.get('/api/myendpoint/', { headers: {
Authorization: `Bearer ${_accessToken}`
} } ).then((response)=>{
resolve(response.data);
});
403 Forbidden error at this point
If you run the front end app now, you will get 403 forbidden error. That's because you need to specify permission in your api view class.
- Specify permission in API view class. We can refer to 'Permission section in Django OAuth Toolkit documentation' for different kinds of permissions. For example, in my views.py, I added below two lines to my model viewset class. The required_scope is the condition the access token has to meet. If the access token I initially requested in javascript was only 'write' scope instead of 'read' scope, then it won't satisfy this condition so it will get 403 forbidden error.
class MyClassViewSet(viewsets.ModelViewSet)
# ...
permission_classes = [TokenHasScope]
required_scopes = ['read']
# ...
Now the 403 error is also gone. Everything is acting normal and we have some protection for our backend API that only our front end application can access.