2
votes

I'm trying to authenticate and authorise a user for an Azure function that's created and exposed using an Azure Static Web App, using Azure AD and MSAL. The user can succesfully access the API if I configure the app to use the older AAD v1 flow but not with MSAL. The setup/use-case:

  • a Single Page Application (SPA) deployed and hosted as an Azure Static Web App using basic HTML and JS (this is a demo 'Hello World' app)
  • The app has authentication integrated using MSAL. Specifically msal-browser.js version 2.6.1. An identity token is retrieved using:
msal.PublicClientApplication(msalConfig).loginPopup(loginRequest)

where msalConfig contains:

```
auth: {
        clientId: "<CLIENTID>",
        authority: "https://login.microsoftonline.com/<TENANT_ID>"
    }
```
  • The user is authenticated and an identity token returned.

  • The static web app exposes a sample function GetMessage which returns some dummy text

  • If the route to the function is unprotected the SPA can call the function successfully and the text is returned to the browser/SPA

  • If the route to the function is protected via routes.json the request to the function (correctly) returns a 401 unless the user is authenticated and authorised.

    {
        "routes": [
          {
            "route": "/api/*",
            "allowedRoles": ["Authenticated"]
          }  
        ]
      }
    

To authenticate the user via MSAL I am attempting to retrieve an access token which I put into the Bearer header of the function call:

```
async function getAPI() {
    const currentAcc = myMSALObj.getAccountByHomeId(accountId);
    if (currentAcc) {
        const response = await getTokenPopup(silentRequest, currentAcc).catch(error => {
            console.log(error);
        });
        console.log("Got token " + response.accessToken)
        const accToke = response.accessToken
        const headers = new Headers();
        const bearer = `Bearer ${accToke}`;

         headers.append("Authorization", bearer);

          const options = {
                method: "GET",
                headers: headers
      };


    let { text } = await( await fetch('/api/GetMessage',options)).json();
    document.querySelector('#name').textContent = text;
    }    
}

```

The token is retrieved and validates in jwt.ms but the function always returns 403 - forbidden. It appears to make no difference if change scopes or user roles although it's possible there's a magic combination I'm missing.

This process works perfectly if the function I'm calling is the Micrsoft Graph - i.e. https://graph.microsoft.com/v1.0/me - it's only failing on our own static web apps function. I can't see a way of accessing logs on the Azure server side to understand why it might be failing.

Using the AAD v1 flow i.e. calling http://APP_URL/.auth/login/aad works perfectly - but it doesn't use the access token. It uses a Cookie called StaticWebAppsAuthCookie (a single call to APP_URL/.auth/login/aad is enough to authenticate and authorise the user). An example of that can be found here

I understood that MSAL was the flow Azure AD was moving toward so is there way to authorise the user via an MSAL flow? Speficially using Azure AD, a static web app and a function exposed within the static web app (not as a standalone Azure Function app).

1

1 Answers

0
votes

I believe when calling an Azure Function API contained within your Static Web App, the service inserts its own auth token into the header. If you are relying on the Authorization Bearer header to pass your token from app to api, it may be overwritten.

In my case, I was sending token:

{
  "typ": "JWT", 
  "alg": "RS256", 
  "kid": "{key value}"
}
{
  "iss": "https://{domain}.b2clogin.com/{tenant ID}/v2.0/", 
  "aud": "{client ID}",
  ...
}

but my Azure Functions API was receiving token:

{
  "typ": "JWT", 
  "alg": "HS256", 
}
{
  "iss": "https://{ID}.scm.azurewebsites.net", 
  "aud": "https://{ID}.azurewebsites.net/azurefunctions",
  ...
}

This issue is being tracked here: https://github.com/Azure/static-web-apps/issues/34