Sourcing JWTs from Azure AD - loum/jwt-auth GitHub Wiki

Azure AD is one of many cloud based directory and identity management service available that can provide JWTs to access system resources.

Don't worry too much about Azure AD. In this particular scenario, we will simply leverage the OAuth 2.0 Client Credentials Grant where a client’s credentials are used to authenticate a request for an access token.

Note: This grant should only be used by trusted clients.

Preliminaries

First, you will need to have access to an Azure AD tenant. Every Azure subscription has an associated Azure Active Directory tenant. If you don't already have an Azure subscription, you can get a free subscription by signing up at https://azure.microsoft.com

Log into the Azure Management portal with your user account and register a new web server app.. You will also need to generate an app secret so clients can authenticate with your server app (also detailed in link above).

Note: The app secret is only displayed once and does not persist across Azure AD sessions. Make sure you record it.

Each app registered in Azure AD receives a Client ID and secret. The Client ID and secret will be used to generate the JWT via an Azure AD defined OAuth 2.0 token endpoint that generally takes the form https://login.microsoftonline.com/<tenant_value>/oauth2/token.

With Client ID, app secret and token endpoint, we can configure the auth/settings.py file under AZURE_AD:

AZURE_AD = {
    'CLIENT_ID': '12345678-abcd-1234-1234-abcdefghijkl',
    'CLIENT_SECRET': '********************************************',
    'TOKEN_URL': 'https://login.microsoftonline.com/<tenant_id>/oauth2/token',
    'RESOURCE_URI': 'https://graph.windows.net',
}

Note: update the RESOURCE_URI to suit your Azure AD tenant. https://graph.windows.net worked for me after much trial-and-error as this feature is not very well documented by Microsoft ...

Azure AD Token Generation

Restart your Django server and make the following request.

$ curl -X POST -d "username=<username>&password=<password>" http://<your_server_ip>:8000/api-azure-token-auth/
{
    "scope": ["User.Read"],
    "token_type": "Bearer",
    "expires_at": 1452228498.2767015,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiIwMDAwMDAwMi0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC81ZmZiMzI0Zi1hYWNiLTQ4NDMtOTViNy02NWZiYzg5ZTY0NzUvIiwiaWF0IjoxNDUyMjI0NTk3LCJuYmYiOjE0NTIyMjQ1OTcsImV4cCI6MTQ1MjIyODQ5NywiYXBwaWQiOiI4ZTdlYTQ3MC1jYjBjLTRjNjUtYWIzZC1mOTFjZjAxYTg2ZDMiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC81ZmZiMzI0Zi1hYWNiLTQ4NDMtOTViNy02NWZiYzg5ZTY0NzUvIiwib2lkIjoiZTEwZDc1NDQtNDZkMy00OGY3LTgxZmItNjc0MDJjZWE2OWUxIiwic3ViIjoiZTEwZDc1NDQtNDZkMy00OGY3LTgxZmItNjc0MDJjZWE2OWUxIiwidGlkIjoiNWZmYjMyNGYtYWFjYi00ODQzLTk1YjctNjVmYmM4OWU2NDc1IiwidmVyIjoiMS4wIn0.FhfrJQVaUrkxE3ePdV6dZRs9SEWNVybJmKtGiv7eH-VT3-m1W88EeAkKEfng-ZuNudOPPJ8CZtHkjXEszL2tRnCIuuHSxEYecQ83SvdiXF97DOwRWGRJ4E4AUy0ZE5-WgYSMwPdtsePc1mFkn_Lflc92DtsGSRq9D8tGAVdulnALEWdZoPvCbM4YsPMngq-WXeTuhdR0IaMZq2gtoAFAljTr7yDVHDecaxZeyaS6nrP6ED4EVkQlU5SQ7efs8tvT5dWOhnAZ87s6FylvpUBL_cHtALsnMqigEQ6P0LNND7nwf1-_VNkuNo6K8krNBNzr69wK8R3f9dfG_tCA48cHHQ",
    "not_before":"1452224597",
    "resource": "00000002-0000-0000-c000-000000000000",
    "expires_on": "1452228497",
    "expires_in": "3600"
}

Note that the return payload is different in this case as it is a simple pass-through from of the Azure AD response. Our token under the access_token key.

Azure AD Token Verification

We'll use the same verification endpoint:

$ curl -X POST -H "Content-Type: application/json" -d '{"token":"<TOKEN>"}' http://<your-server-ip>:8000/api-token-verify/

However, due to the nature of the Azure JWT payload we will need to provide additional configuration.

Note: Although the verification endpoint is the same for symmetric/asymmetrically signed JWTs and Azure AD style JWTs only one JWT scenario can be verified at any one time.

The Azure AD Token

Base64 decoding the JWT presents the payload component. The access_token from the above JWT has been decoded here:

{
  "aud": "00000002-0000-0000-c000-000000000000",
  "iss": "https://sts.windows.net/5ffb324f-aacb-4843-95b7-65fbc89e6475/",
  "iat": 1452224597,
  "nbf": 1452224597,
  "exp": 1452228497,
  "appid": "8e7ea470-cb0c-4c65-ab3d-f91cf01a86d3",
  "appidacr": "1",
  "idp": "https://sts.windows.net/5ffb324f-aacb-4843-95b7-65fbc89e6475/",
  "oid": "e10d7544-46d3-48f7-81fb-67402cea69e1",
  "sub": "e10d7544-46d3-48f7-81fb-67402cea69e1",
  "tid": "5ffb324f-aacb-4843-95b7-65fbc89e6475",
  "ver": "1.0"
}

The aud and appid keys impact the standard verification flow as discussed below:

  • Audience Claim (aud): the intended recipient of the token. This is an optional claim that, if present, must be used by the application that receives the token to verify that the audience value is correct and reject any tokens intended for a different audience. As such, our server must be aware of the audience value. In the example payload above, this value is 00000002-0000-0000-c000-000000000000. Add this to the auth.settings JWT_AUTH global:

      JWT_AUTH = {
          ....
          'JWT_AUDIENCE': '00000002-0000-0000-c000-000000000000',
      }
    

Note: the audience value maps to the Azure AD application APP ID URI

  • Application ID (appid): identifies the application that is using the token to access a resource. Before the JWT will be accepted, the appid must be added as a user via http://<your_server_ip>:8000/admin/auth/user/add/

Azure AD Federation Metadata

More information around the Federation Metadata can be found here. The important component for us is the Token Signing Certificates. Consistent with asymmetrical JWT signing, the Azure AD certificates (there are generally many) must be added to our certificate store. Here, we simply need to configure the Federation Metadata Document URL. This needs to be placed under the auth.settings AZURE_AD global:

    AZURE_AD = {
        ....
        'FEDERATION_METADATA':
            'https://login.microsoftonline.com/5ffb324f-aacb-4843-95b7-65fbc89e6475/federationmetadata/2007-06/federationmetadata.xml'
    }

The Django web server will extract the certs automatically. Restart the Django web server to ensure the settings take effect.

⚠️ **GitHub.com Fallback** ⚠️