Identity for Django#

Prerequisite#

Create a hello world web project in Django.

You can use Django’s own tutorial, part 1 as a reference. What we need are basically these steps:

  1. django-admin startproject mysite

  2. python manage.py migrate

  3. python manage.py runserver localhost:5000 You must use a port matching your redirect_uri that you registered.

  4. Now, add an index view to your project. For now, it can simply return a “hello world” page to any visitor:

    from django.http import HttpResponse
    def index(request):
        return HttpResponse("Hello, world. Everyone can read this line.")
    

Django configuration#

  1. Firstly, create an instance of the identity.django.Auth object, and assign it to a global variable inside your settings.py:

    import os
    from dotenv import load_dotenv
    from identity.django import Auth
    load_dotenv()
    AUTH = Auth(
        os.getenv('CLIENT_ID'),
        client_credential=os.getenv('CLIENT_SECRET'),
        redirect_uri=os.getenv('REDIRECT_URI'),
        ...,  # See below on how to feed in the authority url parameter
        )
    

    Tip

    We recommend storing settings in environment variables.. The snippet above read data from environment variables.

    Initializing Auth object differently based on Identity Provider type

    Its authority URL looks like

    Initialize Auth() object like this

    Microsoft Entra ID

    https://login.microsoftonline.com/tenant

    Auth(…, authority=url, …)

    Microsoft Entra External ID

    https://contoso.ciamlogin.com/contoso.onmicrosoft.com

    Microsoft Entra External ID with Custom Domain

    https://contoso.com/tenant

    Auth(…, oidc_authority=url, …)

    Azure AD B2C

    N/A

    Auth(…, b2c_tenant_name=”contoso”, b2c_signup_signin_user_flow=”susi”)

  2. Inside the same settings.py file, add "identity" into the INSTALLED_APPS list, to enable the default templates came with the identity package:

    INSTALLED_APPS = [
        ...,
        "identity",
    ]
    
  3. Add the built-in views into your urls.py:

    from django.conf import settings
    
    urlpatterns = [
        settings.AUTH.urlpattern,
        ...
        ]
    

Django Web App Sign In#

  1. In your web project’s views.py, decorate some views with the identity.django.Auth.login_required() decorator:

    from django.conf import settings
    
    @settings.AUTH.login_required
    def index(request, *, context):
        user = context['user']
        return HttpResponse(f"Hello, {user.get('name')}.")
    

Web app that logs in users and calls a web API on their behalf#

  1. Decorate your token-consuming views using the same identity.django.Auth.login_required() decorator, this time with a parameter scopes=["your_scope_1", "your_scope_2"].

    Then, inside your view, the token will be readily available via context['access_token']. For example:

    @settings.AUTH.login_required(scopes=["your_scope"])
    def call_api(request, *, context):
        api_result = requests.get(  # Use access token to call a web api
            "https://your_api.example.com",
            headers={'Authorization': 'Bearer ' + context['access_token']},
            timeout=30,
        ).json()  # Here we assume the response format is json
        ...
    

All of the content above are demonstrated in this django web app sample.

API for Django web projects#

class identity.django.Auth(*args, **kwargs)#

A long-live identity auth helper for a Django web project.

Afterwards, all you need to do is to insert auth.urlpattern into your project’s urlpatterns list in your_project/urls.py.

__init__(*args, **kwargs)#

Create an identity helper for a web application.

Parameters:
  • client_id (str) – The client_id of your web application, issued by its authority.

  • client_credential (str) – It is somtimes a string. The actual format is decided by the underlying auth library. TBD.

  • oidc_authority (str) – The authority which your app registers with your OpenID Connect provider. For example, https://example.com/foo. This library will concatenate /.well-known/openid-configuration to form the metadata endpoint.

  • authority (str) – The authority which your app registers with your Microsoft Entra ID. For example, https://example.com/foo. Historically, the underlying library will sometimes automatically append “/v2.0” to it. If you do not want that behavior, you may use oidc_authority instead.

  • redirect_uri (str) –

    This will be used to mount your project’s auth views accordingly.

    For example, if your input here is https://example.com/x/y/z/redirect, then your project’s redirect page will be mounted at “/x/y/z/redirect”, login page will be at “/x/y/z/login”, and logout page will be at “/x/y/z/logout”.

  • b2c_tenant_name (str) – The tenant name of your Microsoft Entra ID tenant, such as “contoso”. Required if your project is using Microsoft Entra ID B2C.

  • b2c_signup_signin_user_flow (str) – The name of your Microsoft Entra ID tenant’s sign-in flow, such as “B2C_1_signupsignin1”. Required if your project is using Microsoft Entra ID B2C.

  • b2c_edit_profile_user_flow (str) – The name of your Microsoft Entra ID tenant’s edit-profile flow, such as “B2C_1_profile_editing”. Optional.

  • b2c_edit_profile_user_flow – The name of your Microsoft Entra ID tenant’s reset-password flow, such as “B2C_1_reset_password”. Optional.

get_edit_profile_url()#

A helper to get the URL for Microsoft Entra B2C’s edit profile page.

You can pass this URL to your template and render it there.

login_required(function=None, /, *, scopes: List[str] = None)#

A decorator that ensures the user to be logged in, and optinoally also have consented to a list of scopes.

A user not meeting the requirement(s) will be brought to the login page. For already logged-in user, the view will be called with a keyword argument named “context” which is a dict containing the user object.

Usage:

@settings.AUTH.login_required
def my_view(request, *, context):
    return render(request, 'index.html', dict(
        user=context["user"],  # User is guaranteed to be present
            # because we decorated this view with @login_required
        ))
Parameters:

scopes (list[str]) –

A list of scopes that your app will need to use. When present, the context will also contain an “access_token”, “token_type”, and likely “expires_in” and “refresh_token”.

Usage:

@settings.AUTH.login_required(scopes=["scope1", "scope2"])
def my_view2(request, *, context):
    api_result = requests.get(  # Use access token to call an api
        "https://example.com/endpoint",
        headers={'Authorization': 'Bearer ' + context['access_token']},
        timeout=30,
    ).json()  # Here we assume the response format is json
    ...

logout(request)#

The logout view.

The logout url is also available with the name “identity.django.logout”. So you can use {% url "identity.django.logout" %} to get the url from inside a template.