Skip to main content

Using Azure Active directory as an OAuth2 provider for Django

Azure Active Directory is a great product and is invaluable in the enterprise space. In this article we'll be setting it up to provide tokens for the OAuth2 client credentials grant. This authorization flow is useful when you want to authorize server-to-server communication that might not be on behalf of a user.

This diagram, by Microsoft, shows the client credentials grant flow.
From Microsoft documentation
 The flow goes like this:
  1. The client sends a request to Azure AD for a token
  2. Azure AD verifies the attached authentication information and issues an access token
  3. The client calls the API with the access token. The API server is able to verify the validity of the token and therefore the identity of the client.
  4. The API responds to the client

Setting up Azure AD as an OAuth2 identity provider

The first step is to create applications in your AD for both your API server and the client. You can find step-by-step instructions on how to register the applications on the Microsoft page at Integrating applications with Azure Active Directory.

We need to generate a key for the client. From your AD blade in the Azure portal click on "app registrations" and then show all of them. Select the client application and then open it's settings. Create a new key and make sure you copy its value after saving.

Now let's add the permissions that our client will ask for. For this article we're just going to use a basic "access" scope. We're not going to add custom scopes to the server application, but this is easily possible by editing the manifest of the server application.

From the settings page of the client application click on "Required permissions". Click "Add" and in the search box type in the name of the server application that you registered in AD. Select it from the list that appears and choose "access" under the delegated permissions.

Now we have a fully fledged OAuth2 service set up and running, which is awesome. Let's call the Azure AD endpoint to get a token:

You can get the application id by opening up the registered application from your Azure AD portal blade.

Azure should respond with a JSON body that looks something like this:


The access_token value will be a JWT token. If you haven't used them before go check out jwt.io. This is the access token that you will present to your Django backend.

If you copy the access token into the decoder on jwt.io then you'll see that the payload looks like this:

Interlude

Let's take a breather and watch a music video. Because, really, 8 bits should be enough for anybody.

Okay, we're back and we're ready to get Django to authenticate the token.

Validating the token in Django

I'm not going to go into the details of setting up your Django project. There is already an excellent tutorial on their site. I'm going to assume that you have an app in your project to handle the API.

What we need to do is write some custom middleware for Django that will get the access token out of the "Authorization" HTTP header, validate the JWT, and check the signature against Azure's published signature. You can find out more about this on Microsoft's website, but this is actually an OAuth2 standard approach so just about any standards compliant documentation will do.

We'll be using standard libraries to decode the JWT, but in order to properly verify it we will need to have the key. We don't want to hardcode the key into our API because Azure rotates their keys on a regular basis. Luckily the OAuth2 standard provides a defined way for an OAuth2 provider to share this sort of information.

Azure publishes it's OAuth2 configuration file at https://login.microsoftonline.com/common/.well-known/openid-configuration. There is also a configuration file that is specific to your tenant at https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration, but the general one is fine for us.

In that document there is a key called "jwks_url" whose value is a url. On that url you will find the keys that Azure is currently using. We need to retrieve that document and find the key that was used in our access token.

Right, so that's the plan. Decode the JWT to find what key was used to sign it and go fetch the key from Microsoft to verify the token. If anybody is able to trick our DNS and serve us content that we think is from Microsoft then they'll be able to pwn us, but otherwise as long as we trust the response from Microsoft we should be able to trust the JWT token the client gave us.

I placed this into settings.py because swapping Azure AD for any other standard implementation of OAuth2 should be possible by just swapping the configuration endpoint. I also set up Memcached while I was busy in the settings, so I ended up with this extra settings information:


Then I made a new file called checkaccesstoken.py and put it into the "middleware" directory of my API app, making sure that I registered it in settings.py

In the __call__ method of the middleware I call out to the functions that are going to check the token:

To get the token we use the Django request object:

We make sure that the token is a Bearer token by defining a class constant called AUTH_TYPE_PREFIX as having the value "Bearer".

Now that we have the access token we need to check it. You could use any JWT library, but I chose to use "pip install pyjwt cryptography". This JWT library will throw a DecodeError if the token is invalid, which we catch and respond with 401 to the user.

The public key is available over the network so we want to cache it to reduce the I/O time. Microsoft suggests that it is safe to cache for about a day.

Now we're done! We can send a bearer access token to our API and be authenticated. Note that we're not doing any authorization, all we're doing here is authenticating. We haven't tied this client application to any form of role in our application. That's for another day :)

References:
  1. I placed the whole middleware file in a Gist
  2. Microsoft article on client credentials flow
  3. Microsoft advice on validating tokens

Comments

Popular posts from this blog

Separating business logic from persistence layer in Laravel

There are several reasons to separate business logic from your persistence layer.  Perhaps the biggest advantage is that the parts of your application which are unique are not coupled to how data are persisted.  This makes the code easier to port and maintain. I'm going to use Doctrine to replace the Eloquent ORM in Laravel.  A thorough comparison of the patterns is available  here . By using Doctrine I am also hoping to mitigate the risk of a major version upgrade on the underlying framework.  It can be expected for the ORM to change between major versions of a framework and upgrading to a new release can be quite costly. Another advantage to this approach is to limit the access that objects have to the database.  Unless a developer is aware of the business rules in place on an Eloquent model there is a chance they will mistakenly ignore them by calling the ActiveRecord save method directly. I'm not implementing the repository pattern in all its glory in this demo.  

Fixing puppet "Exiting; no certificate found and waitforcert is disabled" error

While debugging and setting up Puppet I am still running the agent and master from CLI in --no-daemonize mode.  I kept getting an error on my agent - ""Exiting; no certificate found and waitforcert is disabled". The fix was quite simple and a little embarrassing.  Firstly I forgot to run my puppet master with root privileges which meant that it was unable to write incoming certificate requests to disk.  That's the embarrassing part and after I looked at my shell prompt and noticed this issue fixing it was quite simple. Firstly I got the puppet ssl path by running the command   puppet agent --configprint ssldir Then I removed that directory so that my agent no longer had any certificates or requests. On my master side I cleaned the old certificate by running  puppet cert clean --all  (this would remove all my agent certificates but for now I have just the one so its quicker than tagging it). I started my agent up with the command  puppet agent --test   whi

Redirecting non-www urls to www and http to https in Nginx web server

Image: Pixabay Although I'm currently playing with Elixir and its HTTP servers like Cowboy at the moment Nginx is still my go-to server for production PHP. If you haven't already swapped your web-server from Apache then you really should consider installing Nginx on a test server and running some stress tests on it.  I wrote about stress testing in my book on scaling PHP . Redirecting non-www traffic to www in nginx is best accomplished by using the "return" verb.  You could use a rewrite but the Nginx manual suggests that a return is better in the section on " Taxing Rewrites ". Server blocks are cheap in Nginx and I find it's simplest to have two redirects for the person who arrives on the non-secure non-canonical form of my link.  I wouldn't expect many people to reach this link because obviously every link that I create will be properly formatted so being redirected twice will only affect a small minority of people. Anyway, here's