ADFS Authentication for Django¶
A Django authentication backend for Microsoft ADFS and Azure AD
- Free software: BSD License
- Homepage: https://github.com/snok/django-auth-adfs
- Documentation: http://django-auth-adfs.readthedocs.io/
Features¶
- Integrates Django with Active Directory on Windows 2012 R2, 2016 or Azure AD in the cloud.
- Provides seamless single sign on (SSO) for your Django project on intranet environments.
- Auto creates users and adds them to Django groups based on info received from ADFS.
- Django Rest Framework (DRF) integration: Authenticate against your API with an ADFS access token.
Contents¶
Installation¶
Requirements¶
- Python 3.5 and above
- Django 1.11 and above
You will also need the following:
- A properly configured Microsoft Windows server 2012 R2 or 2016 with the AD FS role installed or an Azure Active Directory setup.
- A root CA bundle containing the root CA that signed the webserver certificate of your ADFS server if signed by an enterprise CA.
Note
When using Azure AD, beware of the following limitations:
- Users have no email address unless you assigned an Office 365 license to that user.
- Groups are listed with their GUID in the groups claim. Meaning you have to create your groups in Django using these GUIDs, instead of their name.
- Usernames are in the form of an email address, hence users created in Django follow this format.
- You cannot send any custom claims, only those predefined by Azure AD.
Setting up django¶
In your project’s settings.py
add these settings.
AUTHENTICATION_BACKENDS = (
...
'django_auth_adfs.backend.AdfsAuthCodeBackend',
...
)
INSTALLED_APPS = (
...
# Needed for the ADFS redirect URI to function
'django_auth_adfs',
...
# checkout the documentation for more settings
AUTH_ADFS = {
"SERVER": "adfs.yourcompany.com",
"CLIENT_ID": "your-configured-client-id",
"RELYING_PARTY_ID": "your-adfs-RPT-name",
# Make sure to read the documentation about the AUDIENCE setting
# when you configured the identifier as a URL!
"AUDIENCE": "microsoft:identityserver:your-RelyingPartyTrust-identifier",
"CA_BUNDLE": "/path/to/ca-bundle.pem",
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email"},
}
# Configure django to redirect users to the right URL for login
LOGIN_URL = "django_auth_adfs:login"
LOGIN_REDIRECT_URL = "/"
########################
# OPTIONAL SETTINGS
########################
MIDDLEWARE = (
...
# With this you can force a user to login without using
# the LoginRequiredMixin on every view class
#
# You can specify URLs for which login is not enforced by
# specifying them in the LOGIN_EXEMPT_URLS setting.
'django_auth_adfs.middleware.LoginRequiredMiddleware',
)
# You can point login failures to a custom Django function based view for customization of the UI
CUSTOM_FAILED_RESPONSE_VIEW = 'dot.path.to.custom.views.login_failed'
In your project’s urls.py
add these paths:
urlpatterns = [
...
path('oauth2/', include('django_auth_adfs.urls')),
]
This will add these paths to Django:
/oauth2/login
where users are redirected to, to initiate the login with ADFS./oauth2/login_no_sso
where users are redirected to, to initiate the login with ADFS but forcing a login screen./oauth2/callback
where ADFS redirects back to after login. So make sure you set the redirect URI on ADFS to this./oauth2/logout
which logs out the user from both Django and ADFS.
You can use them like this in your django templates:
<a href="{% url 'django_auth_adfs:logout' %}">Logout</a>
<a href="{% url 'django_auth_adfs:login' %}">Login</a>
<a href="{% url 'django_auth_adfs:login-no-sso' %}">Login (no SSO)</a>
OAuth2 and ADFS explained¶
This chapter tries to explain how ADFS implements the OAuth2 and OpenID Connect standard and how we can use this in Django.
OAuth2 vs. OpenID Connect¶
What’s OAuth2?
The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf.
What’s important is that it’s only an authorization framework. It only tells you what the user is allowed to do but it doesn’t tell you who the user is. At its core, there’s nothing in the protocol that gives you info about the user.
To solve this, there’s the OpenID Connect framework.
OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 [RFC6749] protocol. It enables Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner.
So, where the OAuth2 protocol lacks any user identifiable info, OpenID Connect does give you info about who the user is. The access token returned by OpenID Connect is a signed JWT token (JSON Web Token) containing claims about the user.
django-auth-adfs
uses this access token to validate the issuer of the token by verifying the
signature and also uses it to keep the Django users database up to date and at the same time
authenticate users.
Depending on the version of ADFS, there’s support for different pieces of these protocol. The table below tries to list the support in various ADFS versions:
Protocol | ADFS 2012 R2 | ADFS 2016 | Azure AD |
---|---|---|---|
OAuth2 | Yes | Yes | Yes |
OpenID Connect | No** | Yes | Yes |
** ADFS 2012 doesn’t implement OpenID Connect, but it does return the access token as a JWT token, just like OpenID Connect would.
OpenID Connect / OAuth2 Flow support:
Version | ADFS 2012 R2 | ADFS 2016 | Azure AD |
---|---|---|---|
Authorization code grant | Yes | Yes | Yes |
Implicit grant | no | Yes | Yes |
Resource owner password credential | no | Yes | Yes |
Client credential grant | no | Yes | Yes |
References:
The Authorization Code Grant is what django-auth-adfs
uses.
OAuth2 and Django¶
Let’s step through the process of how django-auth-adfs
uses OAuth2 to authenticate
and authorize users.
Note
In all the graphs below, remember that the access token is what contains the info about our user in the form of a signed JWT token.
The OAuth2 RFC 6749 specifies the Authorization Code Grant flow as follows:
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
Note: The lines illustrating steps (A), (B), and (C) are broken into
two parts as they pass through the user-agent.
The flow illustrated includes the following steps:
(A) The client initiates the flow by directing the resource owner's
user-agent to the authorization endpoint. The client includes
its client identifier, requested scope, local state, and a
redirection URI to which the authorization server will send the
user-agent back once access is granted (or denied).
(B) The authorization server authenticates the resource owner (via
the user-agent) and establishes whether the resource owner
grants or denies the client's access request.
(C) Assuming the resource owner grants access, the authorization
server redirects the user-agent back to the client using the
redirection URI provided earlier (in the request or during
client registration). The redirection URI includes an
authorization code and any local state provided by the client
earlier.
(D) The client requests an access token from the authorization
server's token endpoint by including the authorization code
received in the previous step. When making the request, the
client authenticates with the authorization server. The client
includes the redirection URI used to obtain the authorization
code for verification.
(E) The authorization server authenticates the client, validates the
authorization code, and ensures that the redirection URI
received matches the URI used to redirect the client in
step (C). If valid, the authorization server responds back with
an access token and, optionally, a refresh token.
One thing missing in the graph from the RFC is the Resource Server
.
Let’s add it to make things complete:
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
| ^
| |
(F) Access Token
| (G)
v |
+-----------------+
| |
| Resource Server |
| |
+-----------------+
Extra steps
(F) The client makes a protected resource request to the resource
server by presenting the access token.
(G) The resource server validates the access token, and if valid,
serves the request.
Alright, now that we have the entire flow, let’s translate the roles to our components and use a bit more comprehensible terms:
+----------+
| |
| User |
| |
+----------+
^
|
(B) Resource
+----|-----+ & Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| Web | | ADFS |
| Browser -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|---|----+ +---------------+
| | ^ ^ v
(A) (C)(G) | |
| | | | |
^ v | | |
+--------|+ | |
| |>---(D)-- Authorization Code ---------' |
| Django | & Redirection URI |
| Login | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
| ^
| |
(F) Access Token
| (G) Session ID
v |
+-------------------------------+
| |
| Django Authentication Backend |
| |
+-------------------------------+
The following things changed:
- A
resource
parameter was added to step A. This is an ADFS specific thing used to identify which application. - Step G was extended up to the web browser. Resembling the session cookie sent back to the web browser.
Resource Owner
➜User
User-Agent
➜Web Browser
Authorization Serve
➜ADFS Server
Client
➜Django Login
Resource Server
➜Django Authentication Backend
Notice how 2 roles were replaced by “pieces” of Django. Django effectively takes up 2 roles here.
If you were to split Django in 2 parts, it’s login pages and the authentication backends,
then the login pages would map to the Client
role. It wants to get a session for the
user and give it a session cookie.
The authentication backend maps to the Resource Server
role,
authenticating/authorizing the user and creating the session.
The session you can think of as being the protected resource.
Once the session is created, OAuth2 isn’t used anymore. Django uses its sessions to authenticate and authorize the user on subsequent requests.
On the ADFS side, you need to configure both the Client
role part of Django
(called a Native Application in ADFS 4.0), as well as the Resource Server
part
(called a Web Application in ADFS 4.0).
Rest Framework and OAuth2¶
When activating Django Rest Framework integration to protect an API, the roles shift once more.
The example assumes a situation where you use a script or some other application to make requests
to your API. In that case, the OAuth2 flow also changes from the Authorization Code Grant
flow
to the Resource Owner Password Credentials Grant
flow.
Note
If you would call the API from a Single Page Application (SPA), you’ll most likely be using the
Implicit Grant
flow. We won’t explain this flow here, but the principle is sort of the same.
Here’s the RFC explanation again:
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
The flow illustrated includes the following steps:
(A) The resource owner provides the client with its username and
password.
(B) The client requests an access token from the authorization
server's token endpoint by including the credentials received
from the resource owner. When making the request, the client
authenticates with the authorization server.
(C) The authorization server authenticates the client and validates
the resource owner credentials, and if valid, issues an access
token.
Again, let’s add the Resource Server
role to the picture:
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
| ^
| |
(D) Access Token
| (E)
v |
+-----------------+
| |
| Resource Server |
| |
+-----------------+
Extra steps
(D) The client makes a protected resource request to the resource
server by presenting the access token.
(E) The resource server validates the access token, and if valid,
serves the request.
And let’s map it to our components:
+----------+
| |
| User |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+-------------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | ADFS |
| Application | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+-------------+ +---------------+
| ^
| |
(D) Access Token
| (E)
v |
+-----------------------+
| |
| Django Rest Framework |
| API |
| |
+-----------------------+
Let’s go over the changes again:
Resource Owner
➜User
Client
➜Application
Resource Server
➜Django Rest Framework API
In this case, a user inputs his username and password into an application/script. The application fetches an access token on behalf of the user and uses it to make calls to you API.
ADFS and OAuth2 lingo compared¶
Potayto, potahto…
OAuth2 and ADFS don’t keep the same name for components. Below is an overview of what OAuth2 role maps to which configuration part on ADFS.
OAuth2 | Azure AD | ADFS 2016 | ADFS 2012 |
---|---|---|---|
Resource Owner | User | User | User |
Authorization Server | ADFS server | ADFS server | ADFS server |
Client | Native Application |
|
Client |
Resource Server | Web app / API | Web API | Relying Party |
Note
For ADFS 2016, we assumed you use application group configuration instead of the “old-fashion” Relying Party Trust config.
For ADFS 2012, the client part is not visible from the GUI and can only be configured via PowerShell commands.
Settings Reference¶
AUDIENCE¶
- Default:
- Type:
string
orlist
Required
Set this to the value of the aud
claim your ADFS server sends back in the JWT token.
You can lookup this value by executing the powershell command Get-AdfsRelyingPartyTrust
on the ADFS server
and taking the Identifier
value. But beware, it doesn’t match exactly if it’s not a URL.
Examples
Relying Party Trust identifier | aud claim value |
---|---|
your-RelyingPartyTrust-identifier | microsoft:identityserver:your-RelyingPartyTrust-identifier |
https://adfs.yourcompany.com/adfs/services/trust | https://adfs.yourcompany.com/adfs/services/trust |
BLOCK_GUEST_USERS¶
- Default:
False
- Type:
boolean
Whether guest users of your Azure AD is allowed to log into the site. This is validated by matching
the http://schemas.microsoft.com/identity/claims/tenantid
-key in the claims towards the configured tenant.
BOOLEAN_CLAIM_MAPPING¶
- Default:
None
- Type:
dictionary
A dictionary of claim/field mappings that is used to set boolean fields on the user account in Django.
The key represents user model field (e.g. first_name
)
and the value represents the claim short name (e.g. given_name
).
If the value is any of y, yes, t, true, on, 1
, the field will be set to True
. All other values, or the absence of
the claim, will result in a value of False
example
AUTH_ADFS = {
"BOOLEAN_CLAIM_MAPPING": {"is_staff": "user_is_staff",
"is_superuser": "user_is_superuser"},
}
Note
You can find the short name for the claims you configure in the ADFS management console underneath ADFS ➜ Service ➜ Claim Descriptions
CA_BUNDLE¶
- Default:
True
- Type:
boolean
orstring
The value of this setting is passed to the call to the Requests
package when fetching the access token from ADFS.
It allows you to control the webserver certificate verification of the ADFS server.
True
to use the default CA bundle of the requests
package.
/path/to/ca-bundle.pem
allows you to specify a path to a CA bundle file. If your ADFS server uses a certificate
signed by an enterprise root CA, you will need to specify the path to it’s certificate here.
False
disables the certificate check.
Have a look at the Requests documentation for more details.
Warning
Do not set this value to False
in a production setup. Because we load certain settings from the ADFS server,
this might lead to a security issue. DNS hijacking for example might cause an attacker to inject his own
access token signing certificate.
CLAIM_MAPPING¶
- Default:
None
- Type:
dictionary
A dictionary of claim/field mappings that will be used to populate the user account in Django. The user’s details will be set according to this setting upon each login.
The key represents the user model field (e.g. first_name
)
and the value represents the claim short name (e.g. given_name
).
example
AUTH_ADFS = {
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email"},
}
The dictionary can also map extra details to the Django user account using an
Extension of the User model
Set a dictionary as value in the CLAIM_MAPPING setting with as key the name User model.
You will need to make sure the related field exists before the user authenticates.
This can be done by creating a receiver on the
post_save signal that
creates the related instance when the User
instance is created.
example
'CLAIM_MAPPING': {'first_name': 'given_name',
'last_name': 'family_name',
'email': 'upn',
'userprofile': {
'employee_id': 'employeeid'
}}
Note
You can find the short name for the claims you configure in the ADFS management console underneath ADFS ➜ Service ➜ Claim Descriptions
CLIENT_ID¶
- Default:
- Type:
dictionary
Required
Set this to the value you configured on your ADFS server as ClientId
when executing the Add-AdfsClient
command.
You can lookup this value by executing the powershell command Get-AdfsClient
on the ADFS server
and taking the ClientId
value.
CLIENT_SECRET¶
- Default:
None
- Type:
string
A Client secret is generated by ADFS server when executing the Add-AdfsClient
command with the
-GenerateClientSecret
parameter.
You can lookup this value by executing the powershell command Get-AdfsClient
on the ADFS server
and taking the ClientSecret
value.
CONFIG_RELOAD_INTERVAL¶
- Default:
24
- Unit: hours
- Type:
integer
When starting Django, some settings are retrieved from the ADFS metadata file or the OpenID Connect configuration on the ADFS server. Based on this information, certain configuration for this module is calculated.
This setting determines the interval after which the configuration is reloaded. This allows to automatically follow the token signing certificate rollover on ADFS.
CREATE_NEW_USERS¶
- Default:
True
- Type:
boolean
Determines whether users are created automatically if they do not exist.
If set to False
, then you need to create your users before they can log in.
DISABLE_SSO¶
- Default:
False
- Type:
boolean
Setting this to True
will globally disable the seamless single sign-on capability of ADFS.
Forcing ADFS to prompt users for a username and password, instead of automatically logging them in
with their current user. This allows users to use a different account then the one they are logged
in with on their workstation.
You can also selectively enable this setting by using <a href="{% url 'django_auth_adfs:login-no-sso' %}">...</a>
in a template instead of the regular <a href="{% url 'django_auth_adfs:login' %}">...</a>
Attention
This does not work with ADFS 3.0 on windows 2012 because this setting requires OpenID Connect which is not supported on ADFS 3.0
JWT_LEEWAY¶
- Default:
0
- Type:
str
Allows you to set a leeway of the JWT token. See the official PyJWT docs for more information.
CUSTOM_FAILED_RESPONSE_VIEW¶
- Default:
lambda
- Type:
str
orcallable
Allows you to set a custom django function view to handle login failures. Can be a dot path to your Django function based view function or a callable.
Callable must have the following method signature accepting error_message
and status
arguments:
def failed_response(request, error_message, status):
# Return an error message
return render(request, 'myapp/login_failed.html', {
'error_message': error_message,
}, status=status)
GROUP_CLAIM¶
Alias of GROUPS_CLAIM
GROUPS_CLAIM¶
- Default:
group
for ADFS orgroups
for Azure AD - Type:
string
Name of the claim in the JWT access token from ADFS that contains the groups the user is member of. If an entry in this claim matches a group configured in Django, the user will join it automatically.
Set this setting to None
to disable automatic group handling. The group memberships of the user
will not be touched.
Important
If not set to None
, a user’s group membership in Django will be reset to math this claim’s value.
If there’s no value in the access token, the user will be removed from all groups.
Note
You can find the short name for the claims you configure in the ADFS management console underneath ADFS ➜ Service ➜ Claim Descriptions
GROUP_TO_FLAG_MAPPING¶
- Default:
None
- Type:
dictionary
This settings allows you to set flags on a user based on his group membership in Active Directory.
For example, if a user is a member of the group Django Staff
, you can automatically set the is_staff
field of the user to True
.
The key represents the boolean user model field (e.g. is_staff
)
and the value, which can either be a single String or an array of Strings, represents the group(s) name (e.g. Django Staff
).
example
AUTH_ADFS = {
"GROUP_TO_FLAG_MAPPING": {"is_staff": ["Django Staff", "Other Django Staff"],
"is_superuser": "Django Admins"},
}
Note
The group doesn’t need to exist in Django for this to work. This will work as long as it’s in the groups claim in the access token.
GUEST_USERNAME_CLAIM¶
- Default:
None
- Type:
string
When these criteria are met:
- A
guest_username_claim
is configured - Token claims do not have the configured
settings.USERNAME_CLAIM
in it - The
settings.BLOCK_GUEST_USERS
is set toFalse
- The claims
tid
does not matchsettings.TENANT_ID
or claimsidp
does not matchiss
.
Then, the GUEST_USERNAME_CLAIM
can be used to populate a username, when the USERNAME_CLAIM
cannot be found in
the claims.
This can be useful when you want to use upn
as a username claim for your own users,
but some guest users (such as normal outlook users) don’t have that claim.
LOGIN_EXEMPT_URLS¶
- Default:
None
- Type:
list
When you activate the LoginRequiredMiddleware
middleware, by default every page will redirect
an unauthenticated user to the page configured in the Django setting LOGIN_URL
.
If you have pages that should not trigger this redirect, add them to this setting as a list value.
Every item it the list is interpreted as a regular expression.
example
AUTH_ADFS = {
'LOGIN_EXEMPT_URLS': [
'^$',
'^api'
],
}
MIRROR_GROUPS¶
- Default:
False
- Type:
boolean
This parameter will create groups from ADFS in the Django database if they do not exist already.
True
will create groups.
False
will not create any extra groups.
Important
This parameter only has effect if GROUP_CLAIM is set to something other then None
.
RELYING_PARTY_ID¶
- Default:
- Type:
string
Required
Set this to the Relying party trust identifier
value of the Relying Party Trust
(2012) or Web application
(2016) you configured in ADFS.
You can lookup this value by executing the powershell command Get-AdfsRelyingPartyTrust
(2012) or
Get-AdfsWebApiApplication
(2016) on the ADFS server and taking the Identifier
value.
RESOURCE¶
Alias for RELYING_PARTY_ID
RETRIES¶
- Default:
3
- Type:
integer
The number of time a request to the ADFS server is retried. It allows, in combination with TIMEOUT to fine tune the behaviour of the connection to ADFS.
SERVER¶
- Default:
- Type:
string
Required when your identity provider is an on premises ADFS server.
Only one of SERVER
or TENANT_ID
can be set.
The FQDN of the ADFS server you want users to authenticate against.
SETTINGS_CLASS¶
- Default:
django_auth_adfs.config.Settings
- Type:
string
By default, django-auth-adfs reads the configuration from the Django setting
AUTH_ADFS
. You can provide the configuration in a custom implementation
and point to it by using the SETTINGS_CLASS
setting:
# in myapp.adfs.config
class CustomSettings:
SERVER = 'bar'
AUDIENCE = 'foo'
...
# in settings.py
AUTH_ADFS = {
'SETTINGS_CLASS': 'myapp.adfs.config.CustomSettings',
# other settings are not needed
}
The value must be an importable dotted Python path, and the imported object must be callable with no arguments to initialize.
Use cases are storing configuration in database so an administrator can edit the configuration in an admin interface.
TENANT_ID¶
- Default:
- Type:
string
Required when your identity provider is an Azure AD instance.
Only one of TENANT_ID
or SERVER
can be set.
The FQDN of the ADFS server you want users to authenticate against.
TIMEOUT¶
- Default:
5
- Unit: seconds
- Type:
integer
The timeout in seconds for every request made to the ADFS server. It’s passed on as the timeout
parameter
to the underlying calls to the requests
library.
It allows, in combination with RETRIES to fine tune the behaviour of the connection to ADFS.
USERNAME_CLAIM¶
- Default:
winaccountname
for ADFS orupn
for Azure AD. - Type:
string
Name of the claim sent in the JWT token from ADFS that contains the username. If the user doesn’t exist yet, this field will be used as it’s username.
The value of the claim must be a unique value. No 2 users should ever have the same value.
Warning
You shouldn’t need to set this value for ADFS or Azure AD unless you use custom user models.
Because winaccountname
maps to the sAMAccountName
on Active Directory, which is guaranteed
to be unique. The same for Azure AD where upn
maps to the UserPrincipleName
, which is unique
on Azure AD.
Note
You can find the short name for the claims you configure in the ADFS management console underneath ADFS ➜ Service ➜ Claim Descriptions
VERSION¶
- Default:
v1.0
- Type:
string
Version of the Azure Active Directory endpoint version. By default it is set to v1.0
. At the time of writing this documentation, it can also be set to v2.0
. For new projects, v2.0
is recommended. v1.0
is kept as a default for backwards compatibility.
PROXIES¶
- Default:
None
- Type:
dict
An optional proxy for all communication with the server. Example: {'http': '10.0.0.1', 'https': '10.0.0.2'}
See the requests documentation for more information.
ADFS Config Guides¶
Windows 2012 R2 - ADFS 3.0¶
Getting this module to work is sometimes not so straight forward. If your not familiar with JWT tokens or ADFS itself, it might take some tries to get all settings right.
This guide tries to give a basic overview of how to configure ADFS and how to determine the settings for django-auth-adfs. Installing and configuring the basics of ADFS is not explained here.
- ADFS server: https://adfs.example.com
- Web server: http://web.example.com:8000
Step 1 - Configuring a Relying Party Trust¶
From the AD FS Management screen, go to AD FS ➜ Trust Relationships ➜ Relying Party Trusts and click Add Relying Party Trust…

Click Start

Select Enter data about the relying party manually and click Next

Enter a display name for the relying party and click Next.

Select AD FS profile and click Next

Leave everything empty click Next

We don’t need WS-Federation or SAML support so leave everything empty and click Next

Enter a relying party trust identifier and click add. The identifier can be anything but beware, there’s a difference between entering a URL and something else. For more details see the example section of the AUDIENCE setting.
Note
This is the value for the AUDIENCE and the RELYING_PARTY_ID settings.

Select I do not want to configure… and click Next.

Select Permit all users to access the relying party and click Next.

Review the settings and click Next to create the relying party.

Check Open the Edit Claim Rules dialog… and click Close

Step 2 - Configuring Claims¶
If you selected Open the Edit Claim Rules dialog… while adding a relying party, this screen will open automatically. Else you can open it by right clicking the relying party in the list and select Edit Claim Rules…
On the Issuance Transform Rules tab, click the Add Rule button

Select Send LDAP Attributes as Claims and click Next

Give the rule a name and select Active Directory as the attribute store. Then configure the below claims.
LDAP Attribute | Outgoing Claim Type |
---|---|
E-Mail-Addresses | E-Mail Address |
Given-Name | Given Name |
Surname | Surname |
Token-Groups - Unqualified Names | Group |
SAM-Account-Name | Windows Account Name |

Click OK to save the settings
Note
The Outgoing Claim Type is what will be visible in the JWT Access Token. The first 3 claims will go into the CLAIM_MAPPING setting. The 4th is the GROUPS_CLAIM setting. The 5th is the USERNAME_CLAIM setting.
You cannot just copy the outgoing claim type value from this screen and use it in the settings. The name of the claim as it is in the JWT token is the short name which you can find in the AD FS Management screen underneath AD FS ➜ Service ➜ Claim Descriptions
You should now see the rule added. Click OK to save the settings.

Step 3 - Add an ADFS client¶
While the previous steps could be done via the GUI, the next step must be performed via PowerShell.
Pick a value for the following fields.
Name | Example value |
---|---|
Name | Django Application OAuth2 Client |
ClientId | 487d8ff7-80a8-4f62-b926-c2852ab06e94 |
RedirectUri | http://web.example.com/oauth2/callback |
Now execute the following command from a powershell console.
PS C:\Users\Administrator> Add-ADFSClient -Name "Django Application OAuth2 Client" `
-ClientId "487d8ff7-80a8-4f62-b926-c2852ab06e94" `
-RedirectUri "http://web.example.com/oauth2/callback"
The ClientId value will be the CLIENT_ID setting and the RedirectUri value is based on where you
added the `django_auth_adfs
in your urls.py
file.
Step 4 - Determine configuration settings¶
Once everything is configured, you can use the below PowerShell commands to determine the value for the settings of this
package. The <<<<<<
in the output indicate which settings should match this value.
PS C:\Users\Administrator> Get-AdfsClient -Name "Django Application OAuth2 Client"
RedirectUri : {http://web.example.com:8000/oauth2/callback}
Name : Django Application OAuth2 Client
Description :
ClientId : 487d8ff7-80a8-4f62-b926-c2852ab06e94 <<< CLIENT_ID <<<
BuiltIn : False
Enabled : True
ClientType : Public
PS C:\Users\Administrator> Get-AdfsProperties | select HostName | Format-List
HostName : adfs.example.com <<< SERVER <<<
PS C:\Users\Administrator> Get-AdfsRelyingPartyTrust -Name "Django Application" | Select Identifier | Format-List
Identifier : {web.example.com} <<< RELYING_PARTY_ID and AUDIENCE <<<
If you followed this guide, you should end up with a configuration like this.
AUTH_ADFS = {
"SERVER": "adfs.example.com",
"CLIENT_ID": "487d8ff7-80a8-4f62-b926-c2852ab06e94 ",
"RELYING_PARTY_ID": "web.example.com",
"AUDIENCE": "microsoft:identityserver:web.example.com",
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email"},
"USERNAME_CLAIM": "winaccountname",
"GROUP_CLAIM": "group"
}
Enabling SSO for other browsers¶
By default, ADFS only supports seamless single sign-on for Internet Explorer. In other browsers, users will always be prompted for their username and password.
To enable SSO also for other browsers like Chrome and Firefox, execute the following PowerShell command:
[System.Collections.ArrayList]$UserAgents = Get-AdfsProperties | select -ExpandProperty WIASupportedUserAgents
$UserAgents.Add("Mozilla/5.0")
Set-ADFSProperties -WIASupportedUserAgents $UserAgents
After that, restart the ADFS service on every server in the ADFS farm.
For firefox, you’ll also have to change it’s network.automatic-ntlm-auth.trusted-uris
setting
to include the URI of your ADFS server.
Windows 2016 - ADFS 4.0¶
Getting this module to work is sometimes not so straight forward. If your not familiar with JWT tokens or ADFS itself, it might take some tries to get all settings right.
This guide tries to give a basic overview of how to configure ADFS and how to determine the settings for django-auth-adfs. Installing and configuring the basics of ADFS is not explained here.
- ADFS server: https://adfs.example.com
- Web server: http://web.example.com:8000
Step 1 - Configuring an Application Group¶
From the AD FS Management screen, go to AD FS ➜ Application Groups and click Add Application Group…

Fill in a name for the application group, select Web browser accessing a web application and click Next.

Make note of the Client Identifier value. This will be the value for the CLIENT_ID setting.
The Redirect URI value must match with the domain where your Django application is located and the patch where you
mapped the django_auth_adfs
urls in your urls.py
file. If you follow the installation steps from this
documentation, this should be something like https://your.domain.com/oauth2/callback
.

Select Permit everyone and click Next.

Review the settings and click Next
- The Client ID is the value for the CLIENT_ID setting.
- The Relying Party ID is the value for the RELYING_PARTY_ID and AUDIENCE setting.
While they both are the same in this screenshot, they can be changed independently from one another afterwards.

Close the wizard by clicking Close. Our django application is now registered in ADFS.

Step 2 - Configuring Claims¶
Open the properties for the application group we just created. Select the Web application entry and click Edit

On the Issuance Transform Rules tab, click the Add Rule button

Select Send LDAP Attributes as Claims and click Next

Give the rule a name and select Active Directory as the attribute store. Then configure the below claims.
LDAP Attribute | Outgoing Claim Type |
---|---|
E-Mail-Addresses | E-Mail Address |
Given-Name | Given Name |
Surname | Surname |
Token-Groups - Unqualified Names | Group |
SAM-Account-Name | Windows Account Name |

Click Finish to save the settings
Note
The Outgoing Claim Type is what will be visible in the JWT Access Token. The first 3 claims will go into the CLAIM_MAPPING setting. The 4th is the GROUPS_CLAIM setting. The 5th is the USERNAME_CLAIM setting.
You cannot just copy the outgoing claim type value from this screen and use it in the settings. The name of the claim as it is in the JWT token is the short name which you can find in the AD FS Management screen underneath AD FS ➜ Service ➜ Claim Descriptions
You should now see the rule added. Click OK a couple of times to save the settings.
Step 3 - Determine configuration settings¶
Once everything is configured, you can use the below PowerShell commands to determine the value for the settings of this
package. The <<<<<<
in the output indicate which settings should match this value.
PS C:\Users\Administrator> Get-AdfsNativeClientApplication
Name : Django Application - Native application
Identifier : 487d8ff7-80a8-4f62-b926-c2852ab06e94 <<< CLIENT_ID <<<
ApplicationGroupIdentifier : Django Application
Description :
Enabled : True
RedirectUri : {http://web.example.com:8000/oauth2/callback}
LogoutUri :
PS C:\Users\Administrator> Get-AdfsProperties | select HostName | Format-List
HostName : adfs.example.com <<< SERVER <<<
PS C:\Users\Administrator> Get-AdfsWebApiApplication | select Identifier | Format-List
Identifier : {web.example.com} <<< RELYING_PARTY_ID and AUDIENCE <<<
If you followed this guide, you should end up with a configuration like this.
AUTH_ADFS = {
"SERVER": "adfs.example.com",
"CLIENT_ID": "487d8ff7-80a8-4f62-b926-c2852ab06e94",
"RELYING_PARTY_ID": "web.example.com",
"AUDIENCE": "microsoft:identityserver:web.example.com",
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email"},
"USERNAME_CLAIM": "winaccountname",
"GROUP_CLAIM": "group"
}
Enabling SSO for other browsers¶
By default, ADFS only supports seamless single sign-on for Internet Explorer. In other browsers, users will always be prompted for their username and password.
To enable SSO also for other browsers like Chrome and Firefox, execute the following PowerShell command:
[System.Collections.ArrayList]$UserAgents = Get-AdfsProperties | select -ExpandProperty WIASupportedUserAgents
$UserAgents.Add("Mozilla/5.0")
Set-ADFSProperties -WIASupportedUserAgents $UserAgents
After that, restart the ADFS service on every server in the ADFS farm.
For firefox, you’ll also have to change it’s network.automatic-ntlm-auth.trusted-uris
setting
to include the URI of your ADFS server.
Azure AD¶
Getting this module to work is sometimes not so straightforward. If you’re not familiar with JWT tokens or Azure AD itself, it might take some tries to get all the settings right.
This guide tries to give a basic overview of how to configure Azure AD and how to determine the settings for django-auth-adfs. Installing and configuring the basics of Azure AD is not explained here.
Step 1 - Register a backend application¶
After signing in to Azure. Open the Azure Active Directory dashboard.

Note down your Tenant_ID as you will need it later.

Navigate to App Registrations, then click New registration in the upper left hand corner.

Here you register your application.
- The display name of your application.
- What type of accounts can access your application.
- Here you need to add allowed redirect URIs. The Redirect URI value must match with the domain where your Django application is located(eg. http://localhost:8000/oauth2/callback).

When done registering, you will be redirected to your applications overview. Here you need to note down your Client_ID. This is how your Django project finds the right Azure application.

Next we need to generate a Client_Secret. Your application will use this to prove its identity when requesting a token.

Give it a short (display) name. This is only used by you, to help keep track of in case you make more client secrets.

Copy your secret (value). It will be become hidden after a short time, so be sure to note this quickly.

Step 2 - Configuring settings.py¶
We need to update the settings.py
to accommodate our registered Azure AD application.
Replace your AUTH_ADFS with this.
# Client secret is not public information. Should store it as an environment variable.
client_id = 'Your client id here'
client_secret = 'Your client secret here'
tenant_id = 'Your tenant id here'
AUTH_ADFS = {
'AUDIENCE': client_id,
'CLIENT_ID': client_id,
'CLIENT_SECRET': client_secret,
'CLAIM_MAPPING': {'first_name': 'given_name',
'last_name': 'family_name',
'email': 'upn'},
'GROUPS_CLAIM': 'roles',
'MIRROR_GROUPS': True,
'USERNAME_CLAIM': 'upn',
'TENANT_ID': tenant_id,
'RELYING_PARTY_ID': client_id,
}
Add this to your AUTHENTICATION_BACKENDS.
AUTHENTICATION_BACKENDS = [
...
'django_auth_adfs.backend.AdfsAccessTokenBackend',
...
]
Add this path to your project’s urls.py
file.
urlpatterns = [
...
path('oauth2/', include('django_auth_adfs.urls')),
...
]
Step 3 - Register and configure an Azure AD frontend application¶
Just like we did with our backend application in step 1, we have to register a new app for our frontend. In this example we are authenticating a Django Rest Framework token through a single page application(SPA). The redirect URI value must match with the domain where your frontend application is located(eg. http://localhost:3000).
Copy your frontend’s client ID, you will need later

Now we need to add a scope of permissions to our API. Navigate back to app registrations and click on your backend application. Go to Expose an API in the sidebar and press add a scope.
If you have not created an Application ID URI, it will be autogenerated for you. Select it and press save and continue.
Then we will create the actual scope. Call it “read”, and just fill in all the required fields with “read” (maybe write an actual description).
Now we are going to add our frontend application as a trusted app for our backend. Press add a client application

Here you need to paste in your frontend application (client) id.
Now navigate back to app registrations. Click on your frontend application and navigate to API permissions. Press add a permission.
Then we have to press My API’s and then select the backend application. (This could be different if you don’t have owner rights of the backend application.)
Here we can give our frontend the permission scope we created earlier. Press Delegated permissions (should be default) and select the permission you created and press add permission
Finally, sometimes the plugin will need to obtain the user groups claim from MS Graph (for example when the user has too many groups to fit in the access token), to ensure the plugin can do this successfully add the GroupMember.Read.All permission.

Login Middleware¶
django-auth-adfs ships with a middleware class named LoginRequiredMiddleware
.
You can use it to force an unauthenticated user to login and be redirected to the URL specified in in Django’s
LOGIN_URL
setting without having to add code to every view.
By default it’s disabled for the page defined in the LOGIN_URL
setting and the redirect page for ADFS.
But by setting the LOGIN_EXEMPT_URLS
setting, you can exclude other pages from authentication.
Have a look at the Settings Reference for more information.
To enable the middleware, add it to MIDDLEWARE
in settings.py
(or MIDDLEWARE_CLASSES
if using Django <1.10.
make sure to add it after any other session or authentication middleware to be sure all other methods of identifying
the user are tried first.
In your settings.py
file, add the following:
MIDDLEWARE = (
...
'django_auth_adfs.middleware.LoginRequiredMiddleware',
)
AUTH_ADFS = {
...
"LOGIN_EXEMPT_URLS": ["api/", "public/"],
...
}
Rest Framework integration¶
Setup¶
When using Django Rest Framework, you can also use this package to authenticate your REST API clients. For this you need to do some extra configuration.
You also need to install djangorestframework
(or add it to your
project dependencies):
pip install djangorestframework
The default AdfsBackend
backend expects an authorization_code
. The backend
will take care of obtaining an access_code
from the Adfs server.
With the Django Rest Framework integration the client application needs to acquire
the access token by itself. See for an example: Requesting an access token. To
authenticate against the API you need to enable the AdfsAccessTokenBackend
.
Steps to enable the Django Rest Framework integration are as following:
Add an extra authentication class to Django Rest Framework in settings.py
:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'django_auth_adfs.rest_framework.AdfsAccessTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
Enable the AdfsAccessTokenBackend
authentication backend in settings.py
:
AUTHENTICATION_BACKENDS = (
...
'django_auth_adfs.backend.AdfsAccessTokenBackend',
...
)
Prevent your API from triggering a login redirect:
AUTH_ADFS = {
'LOGIN_EXEMPT_URLS': [
'^api', # Assuming you API is available at /api
],
}
(Optional) Override the standard Django Rest Framework login pages in your main urls.py
:
urlpatterns = [
...
# The default rest framework urls shouldn't be included
# If we include them, we'll end up with the DRF login page,
# instead of being redirected to the ADFS login page.
#
# path('api-auth/', include('rest_framework.urls')),
#
# This overrides the DRF login page
path('oauth2/', include('django_auth_adfs.drf_urls')),
...
]
Requesting an access token¶
When everything is configured, you can request an access token in your client (script) and access the api like this:
Note
This example is written for ADFS on windows server 2016 but with some changes in the URLs should also work for Azure AD.
import getpass
import requests
from pprint import pprint
# Ask for password
user = getpass.getuser()
password = getpass.getpass("Password for "+user+": ")
user = user + "@example.com"
# Get an access token
payload = {
"grant_type": "password",
"resource": "your-relying-party-id",
"client_id": "your-configured-client-id",
"username": user,
"password": password,
}
response = requests.post(
"https://adfs.example.com/adfs/oauth2/token",
data=payload,
verify=False
)
response.raise_for_status()
response_data = response.json()
access_token = response_data['access_token']
# Make a request towards this API
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer ' + access_token,
}
response = requests.get(
'https://web.example.com/api/questions',
headers=headers,
verify=False
)
pprint(response.json())
Note
The following example is written for ADFS on windows server 2012 R2 and needs
the requests-ntlm
module.
This example is here only for legacy reasons. If possible it’s advised to upgrade to 2016. Support for 2012 R2 is about to end.
import getpass
import re
import requests
from requests_ntlm import HttpNtlmAuth
from pprint import pprint
# Ask for password
user = getpass.getuser()
password = getpass.getpass("Password for "+user+": ")
user = "EXAMPLE\\" + user
# Get a authorization code
headers = {"User-Agent": "Mozilla/5.0"}
params = {
"response_type": "code",
"resource": "your-relying-party-id",
"client_id": "your-configured-client-id",
"redirect_uri": "https://djangoapp.example.com/oauth2/callback"
}
response = requests.get(
"https://adfs.example.com/adfs/oauth2/authorize/wia",
auth=HttpNtlmAuth(user, password),
headers=headers,
allow_redirects=False,
params=params,
)
response.raise_for_status()
code = re.search('code=(.*)', response.headers['location']).group(1)
# Get an access token
data = {
'grant_type': 'authorization_code',
'client_id': 'your-configured-client-id',
'redirect_uri': 'https://djangoapp.example.com/oauth2/callback',
'code': code,
}
response = requests.post(
"https://adfs.example.com/adfs/oauth2/token",
data,
)
response.raise_for_status()
response_data = response.json()
access_token = response_data['access_token']
# Make a request towards this API
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer %s' % access_token,
}
response = requests.get(
'https://djangoapp.example.com/v1/pets?name=rudolf',
headers=headers
)
pprint(response.json())
Demo¶
A Vagrantfile
and example project are available to show what’s needed to convert a Django project from form based
authentication to ADFS authentication.
Prerequisites¶
A hypervisor like virtualbox.
A working vagrant installation. On Debian 11 (bullseye) if you use the stock vagrant package you need to install these plugins:
vagrant plugin install winrm vagrant plugin install winrm-fs vagrant plugin install winrm-elevated vagrant plugin install vagrant-reload
The github repository should be cloned/downloaded in some directory.
This guide assumes you’re using VirtualBox, but another hypervisor should also work. If you choose to use another one, make sure there’s a windows server 2019 vagrant box available for it.
Components¶
The demo consists of 2 parts:
- A web server VM.
- A windows server 2019 VM.
The webserver will run Django and is reachable at http://web.example.com:8000
. The windows server will run a
domain controller and ADFS service.
Starting the environment¶
Web server¶
First we get the web server up and running.
Navigate to the directory where you cloned/downloaded the github repository.
Bring up the web server by running the command:
vagrant up web
Wait as the vagrant box is downloaded and the needed software installed.
Next, SSH into the web server:
vagrant ssh web
Once connected, start the Django project:
cd /vagrant/demo/adfs python3 manage.py runserver 0.0.0.0:8000
you should now be able to browse the demo project by opening the page http://localhost:8000 in a browser. Pages requiring authentication wont work, because the ADFS server is not there yet.
Note
There are 2 versions of the web example. One is a forms based authentication example, the other depends on ADFS.
If you want to run the forms based example, change the path above to /vagrant/demo/formsbased
ADFS server¶
The next vagrant box to start is the ADFS server. The scripts used for provisioning the ADFS server can be found in the
folder /vagrant
inside the repository.
Navigate to the directory where you cloned/downloaded the github repository.
Bring up the ADFS server by running the command:
vagrant up adfs
Wait as the vagrant box is downloaded and the needed software installed. For this windows box, it takes a couple of coffees before it’s done.
Next, open window showing the login screen of the windows server. The login credentials are:
username: vagrant password: vagrant
Once logged in, install a browser like Chrome of Firefox.
Next, in that browser on the windows server, verify you can open the page http://web.example.com:8000
In the AD FS management console, you can check how the example project is configured. The config is in the Application Groups folder.
Note
You wont be able to test the demo project from outside the windows machine because port 443 is not forwarded and name resolution of adfs.example.com won’t work. You can workaround this by forwarding that port 443 from the guest to port 443 on your host and manually adding the right IP addresses in you hosts file.
Note
Because windows server virtual boxes are rather rare on the vagrant cloud (they need to be rebuild every 180 days),
it might be that the box specified in the Vagrantfile
doesn’t work anymore. If you replace it by another one
that’s just a vanilla windows server, it should work.
Using the demo¶
Once everything is up and running, you can click around in the very basic poll app that the demo is.
The bottom of the page shows details about the logged in user.
There are 2 users already created in the Active Directory domain. Both having the default password
Password123
bob@example.com
which is a Django super user because he’s a member of active directory groupdjango_admins
.alice@example.com
which is a regular Django user.
By default, only the page to vote on a poll requires you to be logged in.
There are no questions by default. Create some in the admin section with user
bob
.Compare the files in
/vagrant/demo/formsbased
to those in/vagrant/demo/adfs
to see what was changed to enable ADFS authentication in a demo project.
Troubleshooting¶
Turn on Django debug logging¶
If you run into any problems, set the logging level in Django to DEBUG.
You can do this by adding the configuration below to your settings.py
You can see this logging in your console, or in you web server log if you’re using something like Apache with mod_wsgi.
More details about logging in Django can be found in the official Django documentation
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(name)s %(message)s'
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose'
},
},
'loggers': {
'django_auth_adfs': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
Run Django with warnings enabled¶
Start the python interpreter that runs you Django with the -Wd
parameter. This will show warnings that are otherwise
suppressed.
python -Wd manage.py runserver
Have a look at the demo project¶
There’s an simple demo project available in the /demo
folder and in the demo chapter of the documentation.
If you compare the files in the adfs
folder with those in the formsbased
folder, you’ll see what needs to be
changed in a standard Django project to enable ADFS authentication.
Besides that, there are a couple of PowerShell scripts available that are used while provisioning the ADFS server for
the demo. you can find them in the /vagrant
folder in this repository. They might be useful to figure out what is
wrong with the configuration of your ADFS server.
Note that they are only meant for getting a demo running. By no means are they meant to configure your ADFS server.
Frequently Asked Questions¶
Why am I always redirected to /accounts/profile/
after login?¶
This is default Django behaviour. You can change it by setting the Django setting named LOGIN_REDIRECT_URL.
How do I store additional info about a user?¶
django_auth_adfs
can only store information in existing fields of the user model.
If you want to store extra info, you’ll have to extend the default user model with extra fields and adjust
the CLAIM_MAPPING setting accordingly.
I’m receiving an SSLError: CERTIFICATE_VERIFY_FAILED
error.¶
double check your CA_BUNDLE
setting. Most likely your ADFS server is using a certificate signed by an
enterprise root CA. you’ll need to put it’s certificate in a file and set CA_BUNDLE
to it’s path.
I’m receiving an KeyError: 'upn'
error when authenticating against Azure AD.¶
In some circumstances, Azure AD does not send the upn
claim used to determine the username. It’s observed to happen
with guest users who’s source in the users overview of Azure AD is Microsoft Account
instead of
Azure Active Directory
.
In such cases, try setting the USERNAME_CLAIM to email
instead of the default upn
. Or create a
new user in your Azure AD directory.
Why am I prompted for a username and password in Chrome/Firefox?¶
By default, ADFS only triggers seamless single sign-on for Internet Explorer or Edge.
Have a look at the ADFS configuration guides for details about how to got this working for other browsers also.
Why is a user added and removed from the same group on every login?¶
This can be caused by having a case insensitive database, such as a MySQL
database with default settings.
You can read more about collation settings
in the official documentation.
The redirect_uri starts with HTTP, while my site is HTTPS only.¶
When you run Django behind a TLS terminating webserver or load balancer, then Django doesn’t know the client arrived
over a HTTPS connection. It will only see the plain HTTP traffic. Therefor, the link it generates and sends to ADFS
as the redirect_uri
query parameter, will start with HTTP, instead of HTTPS.
To tell Django to generate HTTPS links, you need to set it’s SECURE_PROXY_SSL_HEADER
setting and inject the correct
HTTP header and value on your web server.
For more info, have a look at Django’s docs.
I cannot get it working!¶
Make sure you follow the instructions in the troubleshooting guide. It will enable debugging and can quickly tell you what is wrong.
Also, walk through the Settings Reference once, you might find one that needs to be adjusted to match your situation.
Contributing¶
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
Get Started!¶
Types of Contributions¶
You can contribute in many ways:
Report Bugs¶
Report bugs in the issue section of the repository on GitHub.
If you are reporting a bug, please include:
- Detailed steps to reproduce the bug.
- Any details about your local setup that might be helpful in troubleshooting.
Fix Bugs¶
Look through the issues for bugs. Anything tagged with “bug” is open to whoever wants to implement it.
Implement Features¶
Look through the issues for features. Anything tagged with “feature” is open to whoever wants to implement it.
Write Documentation¶
We could always use more documentation, whether as part of the docs or in docstrings in the code.
Submit Feedback¶
The best way to send feedback is to file an issue on GitHub.
If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
Set up your environment¶
- Fork the upstream django-auth-adfs repository into a personal account.
- Install poetry running
pip install poetry
orcurl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
- Configure poetry to create a virtual environment in your project folder:
poetry config virtualenvs.in-project true
- Install dependencies by running
poetry install
- Create a new branch for your changes
- Push the topic branch to your personal fork
- Create a pull request to the django-auth-adfs repository with a detailed explanation