Skip to content

SPA Authorization Code

Gladwin Johnson edited this page Jan 6, 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.

Request an authorization code

The authorization code flow begins with the client directing the user to the /authorize endpoint. In this request, the client requests the openid, offline_access, and https://graph.microsoft.com/mail.read permissions from the user.

// Line breaks for legibility only

https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
&response_type=code
&redirect_uri=https://localhost:44320/
&state=12345
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
&client_id=639982ad-d26c-40a5-852a-80817e3fdae6
Parameter Required/optional Description
tenant required The {tenant} value in the path of the request can be used to control who can sign into the application. The allowed values are common, organizations, consumers, and tenant identifiers. For more detail, see protocol basics. Critically, for guest scenarios where you sign a user from one tenant into another tenant, you must provide the tenant identifier to correctly sign them into the resource tenant.
client_id required The Application (client) ID that the Azure portal – App registrations experience assigned to your app.
response_type required Must include code for the authorization code flow. Can also include id_token or token if using the hybrid flow.
redirect_uri required The redirect_uri of your app, where authentication responses can be sent and received by your app. It must exactly match one of the redirect_uris you registered in the portal, except it must be URL-encoded. For native & mobile apps, you should use one of the recommended values - https://login.microsoftonline.com/common/oauth2/nativeclient (for apps using embedded browsers) or http://localhost (for apps that use system browsers).
scope required A space-separated list of scopes that you want the user to consent to. For the /authorize leg of the request, this can cover multiple resources, allowing your app to get consent for multiple web APIs you want to call.
state recommended A value included in the request that will also be returned in the token response. It can be a string of any content that you wish. A randomly generated unique value is typically used for preventing cross-site request forgery attacks. The value can also encode information about the user's state in the app before the authentication request occurred, such as the page or view they were on.

At this point, the user will be asked to enter their credentials and complete the authentication. The Microsoft identity platform will also ensure that the user has consented to the permissions indicated in the scope query parameter. If the user has not consented to any of those permissions, it will ask the user to consent to the required permissions.

Once the user authenticates and grants consent, the Microsoft identity platform will return a response to your app at the indicated redirect_uri, using the method specified in the response_mode parameter.

Successful response

A successful response using response_mode=query looks like:

GET https://localhost:44320/
?code=0.ARwAkq1F9o3jGk21ENGwmnSoyq2CmWN
&state=12345
&session_state=a0ae5f98-92b
Parameter Description
code The authorization_code that the app requested. The app can use the authorization code to request an access token for the target resource. Authorization_codes are short lived, typically they expire after about 10 minutes.
state If a state parameter is included in the request, the same value should appear in the response. The app should verify that the state values in the request and response are identical.

Redeem a code for an access token

All confidential clients have a choice of using client secrets (symmetric shared secrets generated by the Microsoft identity platform) and certificate credentials(asymmetric keys uploaded by the developer). For best security, we recommend using certificate credentials. Public clients (native applications and single page apps) must not use secrets or certificates when redeeming an authorization code - always ensure that your redirect URIs correctly indicate the type of application and are unique.

Request an access token with a client_secret

Now that you've acquired an authorization_code and have been granted permission by the user, you can redeem the code for an access_token to the desired resource. Do this by sending a POST request to the /token endpoint:

// Line breaks for legibility only

POST /{tenant}/oauth2/v2.0/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

client_id=639982ad-d26c-40a5-852a-80817e3fdae6
&scope=https://graph.microsoft.com/user.read
&code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
&redirect_uri=https://localhost:44320/
&grant_type=authorization_code
&client_secret=JqQX2PNo.........
&return_spa_code=1
Parameter Required/optional Description
tenant required The {tenant} value in the path of the request can be used to control who can sign into the application. The allowed values are common, organizations, consumers, and tenant identifiers. For more detail, see protocol basics.
client_id required The Application (client) ID that the Azure portal – App registrations page assigned to your app.
scope optional A space-separated list of scopes. The scopes must all be from a single resource, along with OIDC scopes (profile, openid, email). For a more detailed explanation of scopes, refer to permissions, consent, and scopes. This is a Microsoft extension to the authorization code flow, intended to allow apps to declare the resource they want the token for during token redemption.
code required The authorization_code that you acquired in the first leg of the flow.
redirect_uri required The same redirect_uri value that was used to acquire the authorization_code.
grant_type required Must be authorization_code for the authorization code flow.
client_secret required for confidential web apps The application secret that you created in the app registration portal for your app. You shouldn't use the application secret in a native app or single page app because client_secrets can't be reliably stored on devices or web pages. It's required for web apps and web APIs, which have the ability to store the client_secret securely on the server side. Like all parameters discussed here, the client secret must be URL-encoded before being sent, a step usually performed by the SDK. For more information on URI encoding, see the URI Generic Syntax specification. The Basic auth pattern of instead providing credentials in the Authorization header, per RFC 6749 is also supported.
return_spa_code required This will force eSTS to return the SPA Authorization Code in the response

Successful response

A successful response using return_spa_code=1 looks like:

{
    "token_type": "Bearer",
    "scope": "email openid profile https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/User.Read",
    "expires_in": 4558,
    "ext_expires_in": 4558,
    "access_token": "eyJ0eXAiOiJKV1QiLCJub25jZSI6Ik5jX1NMLWZCajV0T1hPaExic1BWRkFZTHhWbF9td3dqdGRHaGw0Z...............",
    "spa_code": "0.ARwAkq1F9o3jGk21ENGwmnSoyq2CmWNs0qVAhSqAgX4................."
}
Parameter Description
token type Security token type.
scope If a state parameter is included in the request, the same value should appear in the response. The app should verify that the state values in the request and response are identical.
expires_in Lifetime of the token.
ext_expires_in Extended Lifetime of the token.
access_token An access token is a security token that's issued by an authorization server as part of an OAuth 2.0 flow. It contains information about the user and the resource for which the token is intended.
spa_code The SPA Authorization Code sent from eSTS.

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. Be sure to 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

Desktop/Mobile apps

Web Apps / Web APIs / daemon apps

Advanced topics

News

FAQ

Other resources

Clone this wiki locally