In March 2024, Microsoft renamed Azure Active Directory (Azure AD) to Microsoft Entra ID.
django-azure-auth
is a Django app which wraps the great MSAL
package to enable authentication against Microsoft's Azure Active Directory in Django projects.
The app includes login
, logout
and callback
authentication views, a decorator
to protect individual views, and middleware which allows the entire site to require user
authentication by default, with the ability to exempt specified views.
This project is in no way affiliated with Microsoft.
From PyPi:
pip install django-azure-auth
- Register an app at https://portal.azure.com/.
- Add a client secret and note it down.
- Add a redirect URI of the format
https://<domain>/azure_auth/callback
.
Add the following to your settings.py
, replacing the variables in braces with the values
from your Azure app:
AZURE_AUTH = {
"CLIENT_ID": "<client id>",
"CLIENT_SECRET": "<client secret>",
"REDIRECT_URI": "https://<domain>/azure_auth/callback",
"SCOPES": ["User.Read"],
"AUTHORITY": "https://login.microsoftonline.com/<tenant id>", # Or https://login.microsoftonline.com/common if multi-tenant
"LOGOUT_URI": "https://<domain>/logout", # Optional
"PUBLIC_URLS": ["<public:view_name>",], # Optional, public views accessible by non-authenticated users
"PUBLIC_PATHS": ['/go/',], # Optional, public paths accessible by non-authenticated users
"ROLES": {
"95170e67-2bbf-4e3e-a4d7-e7e5829fe7a7": "GroupName1",
"3dc6539e-0589-4663-b782-fef100d839aa": "GroupName2"
}, # Optional, will add user to django group if user is in EntraID group
"USERNAME_ATTRIBUTE": "mail", # The AAD attribute or ID token claim you want to use as the value for the user model `USERNAME_FIELD`
"EXTRA_FIELDS": [], # Optional, extra AAD user profile attributes you want to make available in the user mapping function
"USER_MAPPING_FN": "azure_auth.tests.misc.user_mapping_fn", # Optional, path to the function used to map the AAD to Django attributes
}
LOGIN_URL = "/azure_auth/login"
LOGIN_REDIRECT_URL = "/" # Or any other endpoint
Make sure you configure the settings.AZURE_AUTH["USERNAME_ATTRIBUTE"]
setting to the AAD attribute or ID token claim you want to use for the USERNAME_FIELD
of your user model. Common choices are mail
, sub
or oid
.
Note
In version 1.x.x this was hardcoded to mail
.
Add the following to your INSTALLED_APPS
:
INSTALLED_APPS = (
"...",
"azure_auth",
"..."
)
Configure the authentication backend:
AUTHENTICATION_BACKENDS = ("azure_auth.backends.AzureBackend",)
Include the app's URLs in your urlpatterns
:
from django.urls import path, include
urlpatterns = [
path("azure_auth/", include("azure_auth.urls"),),
]
To make user authentication a requirement for accessing an individual view, decorate the view like so:
from azure_auth.decorators import azure_auth_required
from django.shortcuts import HttpResponse
@azure_auth_required
def protected_view(request):
return HttpResponse("A view protected by the decorator")
If you want to protect your entire site by default, you can use the middleware by adding the
following to your settings.py
:
MIDDLEWARE = [
"...",
"azure_auth.middleware.AzureMiddleware",
"...",
]
Make sure you add the middleware after Django's session
and authentication
middlewares so
that the request includes the session and user objects. Public URLs which need to be accessed by
non-authenticated users should be specified in the settings.AZURE_AUTH["PUBLIC_URLS"]
, as
shown above.
A common use-case is to save attributes/claims from AAD on fields of the Django user model. Rather than providing a way to configure a 1-to-1 mapping, django-azure-auth
allows you to define a function that takes in the AAD attributes/claims and transform/compose them into Django user model values, in a completely customizable way. As an example, suppose you have the following user model:
class User(AbstractUser):
full_name = models.CharField()
You want to populate the full_name
field using the givenName
and surname
AAD user attributes i.e not a 1-to-1 mapping. You also want to mark the user as staff.
You can do this by simply defining the below function and specifying the settings.AZURE_AUTH["USER_MAPPING_FN"]
setting as the import path of the function:
# main/utils.py
def user_mapping_fn(**attributes):
return {
"first_name": attributes["givenName"] + attributes["surname"],
"is_staff": True,
}
Note
In this example, the USER_MAPPING_FN
setting would be specified as "main.utils.user_mapping_fn".
The attributes passed to the mapping function will include:
- The default user profile attributes https://learn.microsoft.com/en-us/entra/external-id/customers/concept-user-attributes
- The ID token claims https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference
- Any extra user attributes specified in `settings.AZURE_AUTH["EXTRA_FIELDS"]
Important
The mapping function must return a dictionary whose keys are all valid attributes/fields of your user model, otherwise an AttributeError will be raised during authentication.
Adding a group to the Azure Enterprise application will pass the group id down to the application via the token.
This happens only, if the user is part of the group. In this case the group will be listed in the token
.
On how to configure this in Azure see here: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-group-claims
Groups available in the token are synced with the corresponding django groups. Therfor the group id's from Azure need to be mapped in the
settings with the Django groups by adding the following to AZURE_AUTH
in settings
.
"ROLES": {
"95170e67-2bbf-4e3e-a4d7-e7e5829fe7a7": "GroupName1",
"3dc6539e-0589-4663-b782-fef100d839aa": "GroupName2"
}
If a user is assigned to one or more of this groups listed in the configuration, the user will be added automatically to the respective Django group. The group will be created if it does not exist. If a user is not part of a group (revoke permissions case), but is still in the Django group, the user will be removed from the Django group.
During logout, if the ID token includes only the default claims, Active Directory will present the user with a page prompting them to select the account to log out. To disable this, simply enable the login_hint
optional claim in your client application in Azure, as described in https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#send-a-sign-out-request.
This app is heavily inspired by and builds on functionality in https://github.com/shubhamdipt/django-microsoft-authentication, with both feature improvements and code assurance through testing.
Credit also to: