A Guide to OAuth2.0 Authorization with Django Rest Framework.
So we know the API handles resources important and personal to us. And because of this, some level of security is needed, right? Right. This is where API authentication and authorization come in the picture.
Authentication is the process that verifies the identity of the resource owner. This happens with the the help of an authentication protocol or method. There are different authentication methods which are:
- HTTP Basic Authentication
- API key Authentication
- OAuth Authentication
HTTP Basic Authentication
This is basically the method of authentication that requires the username and the password of the resource owner, before accessing the desired resource.
API Key Authentication
This method of authentication uses a special key that the client uses with every request made for user identification.
OAuth Authentication
(This is exactly where we are heading to) This method generates tokens needed to make certain requests that are permitted by the resource owner to access data.
Authorization is the process of checking if a client application has the necessary rights of making specific requests. (In other words, “hey app, do you have the permission to operate on this resource?”)
Now, the OAuth Authentication… is not really an authentication protocol. It is actually for authorization, but authentication must be successful before authorization.
(Then what exactly is the OAuth whatever?) OAuth is an open standard framework wherein an internet user delegates some rights to an application to enable it make certain requests on personal resources. The first two methods were ways of representing a user to access resources, the third method gives delegates.
There are components of the OAuth authorization framework, they have different roles for OAuth transaction:
- Scope
- Resource Owner
- Resource Server
- Client
- Tokens
Scope
These are permissions written to limit an application’s access to a user’s data. They are defined to limit access for OAuth tokens. (You are only permitted to view my pictures, not delete them)
Resource Owner
This is the end user of an application. They own the protected data in the resource server.
Resource Server
The API server that stores the protected data that the application wants to access.
Client
The client application is the the software that wants to access certain resources from the API server.
Tokens
These will be granted to the client application to access resources, after the resource owner authorizes it. There are two types of tokens: Access and Refresh tokens. As the name implies, the access token is used by the client application to have access to a resource in the API server. While the refresh application requests for a new access token after the old one has expired.
The whole OAuth authorization process is centered on two goals:
- Get a token
- Use the token
The token is the access that has been delegated to the client to carry out limited actions on protected API resources in behalf of the resource owner. This type of token is the access token.
Basically speaking, the use of the OAuth framework is to share resources between two applications. It could be an application that wants to access your online library to know the types for books you love in order to recommend more books to your taste. Or as a developer, a case that’s familiar to me is, CircleCI accessing your Github repos in order to build and test your code.
So before these applications share resources, they have to be familiar with each other(you know, like a brief intro. I can’t just walk into your house and expect you to give me food, without knowing who I am, right?).
The client application, A, that wants the resource has to introduce itself with the service, B, that has the resource. This is where the whole process starts, with the application registration.
A client_id and a client_secret is generated and used to identify A, when it wants something from B.
The client_id is like the identifier, while the client_secret is just like the password app A uses to prove its identity to the authorization server of app B.
A redirect URL should also be given during the registration. This, way, the user will be redirected back to app A after the authorization process is complete.
Now there are methods of communication between the two applications during the OAuth authorization process flow. These methods are called Grant Types:
- Authorization Code Grant Type
- PKCE
- Refresh Token
- Device Code
- Client Credential
(You can check them out here )
Out of all, the most popular is the Authorization Code Grant Type. This grant type allows the client application give out an authorization code to the authorization server in exchange for an access token.
The authorization code is obtained and given to the client application after the user(resource owner) authorizes it to carry out whatever action it wants.
Steps in the Authorization Code Flow
The client requests for authorization from the resource owner at the authorization server with the help of a link that’s similar to:
https://authorization_server.com/oath/authorize/
?response_type=code& client_id=0hn3BKiQty9uuu4aSu2N5oDQxDn0mhG5MQmwSzEd& redirect_uri=http://client_application.com/
(Here, ‘https://authorization_server.com’ is basically the server the OAuth API service is running on, and value of the reponse_type is actually set to “code”, literally. This way, the client tells the server it wants an authorization code as the response)
After the user clicks on the authorization request link, a page comes up showing the client application is trying access service B resources with whatever permissions it needs.
The user approves or rejects the requests of the client application.
If approved, the user is redirected to the client application page because of the redirect URL, with a code as the query string. This is the code that will be exchanged for an access token, when the client makes a POST request to the authorization server.
The POST request is similar to what’s below:
POST /oauth/token/ HTTP/1.1 Host: authorization_server.com Content-Type: application/x-www-form-urlencoded Accept: / client_id=0hn3BKiQty9uuu4aSu2N5oDQxDn0mhG5MQmwSzEd& grant_type=authorization_code& client_secret=1HtAUyLOWJgWsHENesaPu2ysjkwKXE0Bwlpm1jZDtK4Tc5EDNZ8ClfedYzXRKwtGLZY7bADwm6O0gt1YH7BIwTipO9SpzBUAHj3UWMs1Tbm66s6AQehiaH8m14IGPuan& code=qCJJ4gFLvaaeqfDhHMoeD2JIBUYbDe& redirect_uri=http://client_application.com/
The authorization server will give a response that contains the access token along with a refresh token. This is similar to:
{
"access_token": "eaZ5Z14gauu8ScIg8Ss0LXI9ROBaCb",
"expires_in": 36000,
"token_type": "Bearer",
"scope": "read write",
"refresh_token": "2hlb5nJq8TpfSWGc5cxaboKBo5gvZ2"
}
To access a resource with the access token:
curl --request GET \
--url http://<the_resource_endpoint>/ \
--header 'Authorization: Bearer <access token>' \
--form data_key=Data_value \
These are simple steps provided by OAuth framework for authorization. Looking at the steps previously given, we can see that none had any relationship to user’s credential, making this method of accessing a resource a safe one.
The given authorization steps can easily be replicated on our previously created repository, you can clone it from here.
So here’s how to implement OAuth authorization framework on an API service(Django style).We’ll be trying it on our previous code… you can use the steps to try on yours if you have any.
Django OAuth Toolkit is a 3rd party application that helps you to easily turn your service into an OAuth2.0 one. It can be installed in one of the following ways:
$ docker-compose exec web pipenv install django-oauth-toolkit # or
$ pip install django-oauth-toolkit # or
$ pipenv install django-oauth-toolkit
(Well it depends on how you chose to package your application)
After installation, we have to let Django know about the installed application.
## settings.py
INSTALLED_APPS = [
...,
...,
'oauth2_provider', # Django OAuth2 app
]
And if you’re using Docker, don’t forget to build:
$ docker-compose build
Now it’s time for the OAuth settings( this is like making your application application an Oauth one on a default level). Now in your application’s settings.py file, what you do is:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], 'DEFAULT_AUTHENTICATION_CLASSES': ( 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', # OAuth2Authentication backend for the API
),
}OAUTH2_PROVIDER = {
# this is the dictionary of available scopes that will be visible to the user
'SCOPES': {
'images': 'Create and get images',
'image': 'Read and delete an image'
}
}MIDDLEWARE = [
...
... 'oauth2_provider.middleware.OAuth2TokenMiddleware', # to check for tokens in requests
] # the above middleware must come after #'django.middleware.security.SecurityMiddleware' and before #'django.middleware.common.CommonMiddleware'
Now what happened here, was that we made the image service in this case to be an OAuth provider, and defined the list of scopes(permissions) that will be needed to work on certain resources in the service.
(Don’t forget to add the oauth app’s url in your project’s url configuration file)
urlpatterns = [path('oauth/',include('oauth2_provider.urls',namespace='oauth2_provider')),
]
Recall that I said for applications that communicate with each other, there has to be some type of introductory stage(remember?). This way the OAuth service knows which app is which when they start asking for data (that’s just how it goes).
So if a 3rd party client application wanted to access whatever resource it wanted, it would have to register itself with our image service. This can be done with the link below:
http://0.0.0.0:8000/oauth/applications/register # or http://127.0.0.1:8000/oauth/applications/register
We would see a page that’s similar to what’s below:
Here we see that the client id and client secret are already generated for whatever app that wants our service. All that the owner of the client application have to do is to provide the necessary details.
When the application is registered, it can access the service resources with the permission of the user, but first it would have to request for permission from the user with a GET request. This can be done with the link below:
GET http://0.0.0.0:8000/oauth/authorize/
?client_id=<your_client_id_from_app_registration> &response_type=code
&redirect_uri=<the_redirect_uri_from_app_registration>
We would see a page that’s similar to what’s below:
The list of permissions you see here are the list of scopes we defined in our settings, remember? They define what the client application can do with our images, when it gets the access token.
(Don’t worry, we’ll see where we defined the scope for our images)
When the user(resource owner) clicks “Authorize”, a code is generated for the client to present to the authorization server. In our case, we would see a page that looks like:
This is because there’s no ”http://0.0.0.0:8000/testwebappfakeurl” running on our local server. It would be different if it was a real URL on another server. Here we see the “code=JKQkPbds6rMXQYum2UheWj00DNhk37” (you guessed right, that’s the authorization code).
It is what the client application presents to the authorization server, telling it(server) that the user gave it a thumbs-up to continue with its operations on resources.
With this code, the client app makes a POST request to the server in exchange for an access token:
POST http://0.0.0.0:8000/oauth/token/
client_id=<your_client_id_from_app_registration>
client_secret=<your_client_secret_from_app_registration>
code=<code_given_after_authorization_request>
redirect_uri=<the_redirect_uri_from_app_registration> Response:
{
"access_token": "<access token>",
"expires_in": 36000,
"token_type": "Bearer",
"scope": "images image",
"refresh_token": "<refresh token>"
}
In the response, we see that the access token has been granted the permissions(scopes) to access any resource that asks for ‘images’ or ‘image’ scope before it can be operated on.
Now to see where these scopes were defined in resources:
# image/views.py
from oauth2_provider.contrib.rest_framework import TokenHasScope
class ImageListCreate(ListCreateAPIView): # POST/GET methods
queryset = Image.objects.all()
serializer_class = ImageSerializer
parser_classes = [MultiPartParser] # to accept any media types during POST request
filterset_fields = ['title', 'caption']
permission_classes = [TokenHasScope] # inbuilt OAuth permission from django-oauth-toolkit
required_scopes = ['images'] # custom scope name
class ImageRetrieveDestroyDetailView(RetrieveUpdateDestroyAPIView): # GET{id}/DELETE/PUT methods queryset = Image.objects.all()
serializer_class = ImageSerializer
permission_classes = [TokenHasScope]
required_scopes = ['image']
The client app can POST/GET images or GET/DELETE an image with the access token, because it has the required scopes.
For more details on ‘django-oauth-toolkit’ permissions/scopes, you can check it out here
I hope this article was an easy one for you. And if you ever want to reach out to me, you can find me on Twitter or LinkedIn.
Till then… Happy coding.