Skip to content

SPA Authorization Code

Gladwin Johnson edited this page Jan 7, 2022 · 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 client-side (SPA) authentication, using a confidential client SDK such as MSAL.net or MSAL Node server-side, and MSAL.js client side (e.g., an ASP.net web application hosting a React single-page application). In these scenarios, the application will likely need authentication both client-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

Required Redirect URI setup to support the flow

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

Initial token acquisition

This sample makes use of OpenId Connect hybrid flow, where at authentication time the app receives both sign in info, the id_token and artifacts (in this case, an authorization code) that the app can use for obtaining an access token. This access token can be used to access other resources - in this sample, the Microsoft Graph, for the purpose of reading the user's mailbox.

This sample shows how to use MSAL to redeem the authorization code into an access token, which is saved in a cache along with any other useful artifact (such as associated refresh_tokens) so that it can be used later on in the application from the controllers' actions to fetch access tokens after they are expired.

The redemption takes place in the AuthorizationCodeReceived notification of the authorization middleware. This is the section where the new MSAL.Net WithSpaAuthorizationCode API is used to get the SpaAuthCode Here there's the relevant code:

private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
        {
            try
            {
                // Upon successful sign in, get the access token & cache it using MSAL
                IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
                AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { "user.read" }, context.Code)
                    .WithSpaAuthorizationCode(true)
                    .ExecuteAsync();

                HttpContext.Current.Session.Add("Spa_Auth_Code", result.SpaAuthCode);
            }
            catch
            {
 
            }
        }

Important things to notice:

  • The IConfidentialClientApplication is the primitive that MSAL uses to model the Web application. As such, it is initialized with the main application's coordinates.
  • The scope requested by AcquireTokenByAuthorizationCode is just the one required for invoking the API targeted by the application as part of its essential features.

The IConfidentialClientApplication is created in a separate function in the MsalAppBuilder class.

        public static IConfidentialClientApplication BuildConfidentialClientApplication()
        {
            if (clientapp == null)
            {
                clientapp = ConfidentialClientApplicationBuilder.Create(AuthenticationConfig.ClientId)
                      .WithClientSecret(AuthenticationConfig.ClientSecret)
                      .WithRedirectUri(AuthenticationConfig.RedirectUri)
                      .WithAuthority(new Uri(AuthenticationConfig.Authority))
                      .WithExperimentalFeatures()
                      .Build();
            }
            return clientapp;
        }

Using Spa Auth Code in the Front End

First, configure a new 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))
}

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