Skip to content

SPA Authorization Code

Bogdan Gavril edited this page Mar 20, 2023 · 35 revisions

This flow enables confidential client applications to request an additional "spa auth code" from the eSTS /token endpoint, and this authorization code can be redeemed silently by the front end running in the browser. This feature is intended for applications that perform server-side (web apps) and browser-side (SPA) authentication, using a confidential SDK such as MSAL.net or MSAL Node server-side, and MSAL.js in the browser (e.g., an ASP.net web application hosting a React single-page application). In these scenarios, the application will likely need authentication both browser-side (e.g., a public client using MSAL.js) and server-side (e.g., a confidential client using MSAL.net), and each application context will need to acquire its own tokens.

Today, applications using this architecture will first interactively authenticate the user via the confidential client application, and then attempt to silently authenticate the user a second time with the public client. Unfortunately, this process is both relatively slow, and the silent network request made client-side (in a hidden iframe) will deterministically fail if third-party cookies are disabled/blocked. By acquiring a second authorization code server-side, MSAL.js can skip hidden iframe step, and immediately redeem the authorization code against the /token endpoint. This mitigates issued caused by third-party cookie blocking, and is also more performant

Availability

MSAL 4.40+ supports confidential clients to request an additional "spa auth code" from the eSTS / token endpoint

At a glance

  1. MSAL adds a parameter return_spa_code=1 on the /token call
  2. the STS returns a spa_code alongside the tokens meant for backend to access protected APIs
  3. the front-end SPA gets the spa_code and redeems it for tokens for itself - the SPA can now also access protected APIs

Required Redirect URI setup to support the flow

The redirect_uri used to acquire the spa auth code must be of type web.

Acquire SPA Auth Code in the backend

In MSAL.Net, using the new WithSpaAuthorizationCode API get the SpaAuthCode.

private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{

  // Upon successful sign in, get the access token & cache it using MSAL
  IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder.Create(ClientId).Build();
  AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { "user.read" }, context.Code)
      .WithSpaAuthorizationCode(true)
      .ExecuteAsync();

   // Make the spa code available to the front-end
   HttpContext.Current.Session.Add("Spa_Auth_Code", result.SpaAuthCode);
}

Using Spa Auth Code in the Front End

Configure a PublicClientApplication from MSAL.js in your single-page application:

const msalInstance = new msal.PublicClientApplication({
    auth: {
        clientId: "{{clientId}}",
        redirectUri: "http://localhost:3000/auth/client-redirect",
        authority: "{{authority}}"
    }
})

Next, render the code that was acquired server-side, and provide it to the acquireTokenByCode API on the MSAL.js PublicClientApplication instance. Do not include any additional scopes that were not included in the first login request, otherwise the user may be prompted for consent.

The application should also render any account hints, as they will be needed for any interactive requests to ensure the same user is used for both requests

const code = "{{code}}";
const loginHint = "{{loginHint}}";

const scopes = [ "user.read" ];

return msalInstance.acquireTokenByCode({
    code,
    scopes
})
    .catch(error => {
         if (error instanceof msal.InteractionRequiredAuthError) {
            // Use loginHint/sid from server to ensure same user
            return msalInstance.loginRedirect({
                loginHint,
                scopes
            })
        }
    });

Once the Access Token is retrieved using the new MSAL.js acquireTokenByCode api, the token is then used to read the user's profile

function callMSGraph(endpoint, token, callback) {
    const headers = new Headers();
    const bearer = `Bearer ${token}`;
    headers.append("Authorization", bearer);

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

    console.log('request made to Graph API at: ' + new Date().toString());

    fetch(endpoint, options)
        .then(response => response.json())
        .then(response => callback(response, endpoint))
        .then(result => {
            console.log('Successfully Fetched Data from Graph API:', result);
        })
        .catch(error => console.log(error))
}

Advanced scenario - Bridged SPA Auth code protocol

  1. The app developer adds nativebroker=1 to the /authorize call

Note: MSAL doesn't directly call /authorize endpoint, ASP.NET / ASP.NET Core does this. OWIN allows you to intercept this call to modify the authorization uri.

  1. AAD attempts to connect the user to Windows
  2. MSAL adds a parameter return_spa_code=1 to the /token call (via WithSpaAuthorizationCode API)
  3. Depending or whether AAD managed to connect the user to Windows or not:
  • AuthenticationResult.SpaCode is populated
  • AuthenticationResult.SpaAccountId is populated

Sample

ASP.NET MVC project that uses the SPA Authorization Code in the Front End

Getting started with MSAL.NET

Acquiring tokens

Web Apps / Web APIs / daemon apps

Desktop/Mobile apps

Advanced topics

FAQ

Other resources

Clone this wiki locally