We’re working on setting up integration tests for an application which allows authentication through an AAD token, generated using the web-app authorization mechanism described here. The main problem here is to come up with users for the integration tests that belong to group with different roles, if possible using an actual user to be close to the final product.

Our architecture looks like that:

graph TD;

AD[Active Directory]-- 1: JWT --> Something[The client where users authenticate]
Something -- 2: JWT --> App[Application we want to test]

After much research, and different attempts at soiling AAD’s implementation of oauth by authentication with username/password, we tried to generate a refresh token, which we can then use to generate access tokens. Unfortunately, getting a refresh token when using WebApp AAD authorization is not just a matter of checking the correct box. It is nonetheless possible.

We’re collecting the token through:

graph TD;

AD[Active Directory]-- 1: Refresh token --> Stub[Stub app to collect the token]
Stub -- 2: Refresh token --> Test[Test framework]

We’re using it this way:

graph TD;

Test -- 1: Refresh token --> AD
AD -- 2: JWT --> Test
Test -- 3: JWT --> App[Application we want to test]

Configure the application

I came accross this very nice article detailing how to configure your web app to include this by default, this section is largely paraphrasing it.

The trick is to:

  1. Add delegated permission Read Directory Data to your app registration. That’s done in AAD/App Registrations/[your app]/Required Permissions. You’ll need to click “Grant Permission” (Or have an admin do it if you don’t have permission to do so).

  2. Create a client key and save it somewhere. Copy the application id in the same place

  3. Open the resource explorer, switch to read/write, then search for your application and navigate to the slot you want to use for integration tests (or to generate refresh tokens for whatever reason). Then go to config/auth settings.

    • click edit
    • set client secret to the key you created earlier.
    • set additional login parameters to ["response_type=code id_token", "resource=https://graph.windows.net"]
    • press PUT and wait until page refreshes.

Now the application is configured to generate a refresh token upon authentication. You just need to grab it to be able to use it.

Generate a refresh token

After authenticating, AAD is going to add a header called X-MS-TOKEN-AAD-REFRESH-TOKEN containing the refresh token. You just need to grab it and save it somewhere.

Either create a web app using the same registered application for authorization, or add an endpoint to your application. Getting the token is done through something like that:

const express = require("express");
const PORT = process.env.PORT;

const app = new express();

app.get("/", (req, res) => {
  const refreshToken = req.get("X-MS-TOKEN-AAD-REFRESH-TOKEN");
  res.write(`Refresh token: ${refreshToken}`);
  res.end();
});
  
app.listen(PORT, () => {
  console.log(`Listening on port ${PORT}`);
});

Refresh tokens don’t expire and they can be reused, so you should need to do this only once.

Use the refresh token to generate a new access token

You can now generate a new JWT through a POST on https://login.microsoftonline.com/[tenant]/oauth2/token. tenant is your AAD tenant. This is something in the form of something.onmicrosoft.com.

Use Content-Type: application/x-www-form-urlencoded, and set the body to:

grant_type=refresh_token
&client_id=[Your application id - a guid]
&client_secret=[Application key you generated earlier]
&refresh_token=[The token you just retrieved]

This should return a JSON object containing the JWT:

{
"token_type": "Bearer",
"scope": "Directory.Read.All User.Read",
"expires_in": "3599",
"ext_expires_in": "0",
"expires_on": "1497936822",
"not_before": "1497932922",
"resource": "https://graph.windows.net",
"access_token": "eyJ0e[...]ItA",
"refresh_token": "AQAB[...]IAA"
}

By default this JWT is expiring after an hour. So the plan is to generate that on beginning of test round, then use it throughout testing.

Considerations

Refresh tokens don’t expire. This works well on a test environment.

Do this config only for the slot / environment required for integration test to prevent issues with leaked refresh tokens.