DEV Community

Andrew Elans
Andrew Elans

Posted on • Edited on

Azure Web Apps - frontend token to work with backend

I have started to look into Azure web apps to implement the following scenarion in a single Azure tenant:

  • Frontend web app that shall verify user's credentials, allow or reject the access and send user's requests to a backend web app
  • Backend web app used as an API endpoint that will communicate with Azure SQL db and storage and serve user's requests from the frontend app.

One of the most important things to establish is security and token exchange. I've been struggling a bit finding the right way to connect frontend web app with the backend one, and in this post I share my findings.

For now I just drop some draft notes which will later be properly organizied as I progress...

Frontend web app set up

Provision a new web app in azure called site and url site.azurewebsites.net. Any unauthenticated requests to this url shall be redirected to AAD identity provider.

Configuration

Stack -> Node 22
Startup Command -> pm2 serve /home/site/wwwroot --no-daemon --spa

Authentication

Add Microsoft as identity provider.

Restrict access -> Require authentication
Unauthenticated requests -> Return HTTP 302 Found (Redirect to identity provider)
Redirect to -> Microsoft
Token store -> Enabled
Supported account types -> Current tenant - Single tenant

I use existing app and secret with API permissions set to Microsoft Graph (more details on it later). The same app will be used for the backend web app.

I have also set up a route that enables unauthenticated access using file 'authConfig.json' uploaded to /site/wwwroot with setting excludedPaths in it, modified with tool at resources.azure.com/. The settings are located here:

resources.azure.com/subscriptions/<your-subscription-guid>/resourceGroups/your-rg-name/providers/Microsoft.Web/sites/your-site/config/authsettingsV2/list

There with the provided PUT request tooling you add to platform a new key/value "configFilePath": "authConfig.json" to use the file. More details on it later...

Backend web app set up

Same as the frontend but called api-site and url api-site.azurewebsites.net. In the authentication we choose Microsoft and the same app/secret as for the frontend.

For Unauthenticated requests we choose Return HTTP 401 Unauthorized.

Set also CORS to allow site.azurewebsites.net.

Final working snippet

Method 1

fetch('/.auth/refresh')
.then(() => {
    fetch('/.auth/me')
    .then(res => res.json())
    .then(arr => {
        const idToken = arr[0].id_token;
        fetch('https://api-site.azurewebsites.net/.auth/login/aad', {
            method: "POST",
            headers: {"Content-Type": "application/json"},
            body: JSON.stringify({"access_token": idToken})
        })
        .then(res => res.json())
        .then(authRes => {
                const authToken = authRes.authenticationToken;
                // console.log(authToken);
                fetch('https://api-site.azurewebsites.net/.auth/me', {
                headers: {"X-ZUMO-AUTH": authToken}
            })
            .then(res => res.text())
            .then(console.log)
        })
    })
})
Enter fullscreen mode Exit fullscreen mode

Having logged in to site.azurewebsites.net, open devtools and execute the above code in the console.

Explanation

First we refresh the token by calling site.azurewebsites.net/.auth/refresh, then we obtain id_token by calling site.azurewebsites.net/.auth/me. This id_token is then used to call https://api-site.azurewebsites.net/.auth/login/aad to get back authenticationToken which is passed as X-ZUMO-AUTH header when making API-call to https://api-site.azurewebsites.net.

In the above snippet I send a request to a protected route https://api-site.azurewebsites.net/.auth/me that gives back details of the user logged in to api-site.azurewebsites.net, demonstrating that the flow works.

authenticationToken has exp (Expiration time) 30 days, so in theory it can be used without the need to refresh, but this needs to be checked. So all consequent call to the API-endpoint can be easily done as follows:

// authToken is saved before
fetch('https://api-site.azurewebsites.net/.auth/me', {
    headers: {"X-ZUMO-AUTH": authToken}
})
.then(res => res.text())
.then(console.log)

Enter fullscreen mode Exit fullscreen mode

Method 2 (alternative)

Backend app shall authorize frontend to accept the id_token as mentioned here:

learn.microsoft.com/en-us/azure/app-service/tutorial-auth-aad?pivots=platform-linux#configure-backend-app-service-to-accept-a-token-only-from-the-front-end-app-service

Note: id_token shall be provided, not access_token as mentioned on the linked resource.

To enable the the backend accept the frontend token we may use the above mentioned resources.azure.com where we need to find authsettingsV2 in the backend app here resources.azure.com/subscriptions/SUBSCR_GUID/resourceGroups/RG_NAME/providers/Microsoft.Web/sites/APP_NAME/config/authsettingsV2/list -> find defaultAuthorizationPolicy -> allowedApplications -> add the frontend enterprise application Object ID to the array.

As a result we can call the backend from the frontend with this code resulting in the same response as in Method 1:

fetch('/.auth/me') // calling from your frontend site
.then(res => res.json())
.then(arr => {
    const idToken = arr[0].id_token;

    // calling protected endpoint of the backend app
    fetch('https://api-site.azurewebsites.net/.auth/me', {
        headers: {
            "Content-Type": "application/json",
            'Authorization': `Bearer ${idToken}`
        }
    })
    .then(res => res.text())
    .then(console.log)
})
Enter fullscreen mode Exit fullscreen mode

Top comments (0)