A WIP (work in progress), collected from a variety of sources...
eg. my-app-api
We will refer to it as {nameOfAppRegistration} from this point forward. (This can be pretty much anything but adding the suffix -api may help distinguish it from the spa registration at a later stage.)
- no redirect is required as there is no interactive user sign-in
- Expose an API:
- use the default resource URI api://{clientId} (this is the default when Azure AD v2.0)
- Add a scope (scope name, the display name and description can vary, these are just some reasonable default values that can be used)
- Scope name: access_as_user
- Who can consent?: Admins and users
- Admin consent display name: {nameOfAppRegistration}
- Admin consent description: Allows the app to access {nameOfAppRegistration} as the signed-in user.
- User consent display name: Access {nameOfAppRegistration}
- User consent description: Allow the application to access {nameOfAppRegistration} on your behalf.
- Manifest
- set accessTokenAcceptedVersion to 2
- Copy the Application (Client) Id, we will refer to it as {apiClientId} from this point forward.
-
In Startup.cs
- ConfigureServices
// The following 2 options are equivalent services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd"); // or services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme) .AddMicrosoftIdentityWebApi(Configuration, "AzureAd");
- Configure
// Ordering is important here, UseCors **must** appear before UseAuthentication and UseAuthorization app.UseCors(); app.UseAuthentication(); app.UseAuthorization();
-
Configure appsettings.json
Audience is only necessary when not using the default Resource Uri generated in the step above
"AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "qualified.domain.name", // the directory name "ClientId": "{apiClientId}", "TenantId": "Enter_the_Tenant_Info_Here" // Tenant ID },
eg. my-app-spa
(This can be pretty much anything but adding the suffix -spa may help distinguish it from the api registration above.)
- Authentication: Add a platform, Single-page application, enter a Redirect URI
- API permissions: Add a permission, My APIs, select the API app registration and select the scope created above eg. access_as_user
- Copy the Application (Client) Id, we will refer to it as {spaClientId} from this point forward.
-
Install react-msal
npm install react react-dom npm install @azure/msal-react @azure/msal-browser
-
Configure the environment (envirtonment.ts)
export const environment = { authConfig: { apiClientId: "{apiClientId}", spaClientId: "{spaClientId}", tenantId: "{tenantId}" }, apiConfig: { baseUrl: "{apiBaseUrl}" } };
-
Add an authConfig.ts
import { environment } from './environments/environment'; import { AccountInfo, AuthenticationResult, Configuration, EventType, EventMessage, PublicClientApplication, RedirectRequest, SilentRequest } from "@azure/msal-browser"; const msalConfig: Configuration = { "auth": { "authority": `https://login.microsoftonline.com/${environment.authConfig.tenantId}/`, "clientId": environment.authConfig.spaClientId, "postLogoutRedirectUri": "/", "redirectUri": "/" }, "cache": { "cacheLocation": "localStorage", "storeAuthStateInCookie": true /* setting this to false breaks in IE 11 and Edge */ } } export const pca = new PublicClientApplication(msalConfig); const accounts = pca.getAllAccounts(); if (accounts.length > 0) { pca.setActiveAccount(accounts[0]); } pca.addEventCallback((event: EventMessage) => { if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) { const payload = event.payload as AuthenticationResult; const account = payload.account; pca.setActiveAccount(account); } }); export const loginRequest: RedirectRequest = { scopes: [`api://${environment.authConfig.apiClientId}/.default`] /* request all supported claims */ }
Optional: Add a rudimentary msalApiFetch, call this to have the bearer added by default or refreshed as needed for API calls
const baseUrl = environment.apiConfig.baseUrl; const authService = { GetToken(): Promise<AuthenticationResult> { let tokenRequest: SilentRequest = { account: pca.getActiveAccount() as AccountInfo, scopes: [`api://${environment.authConfig.apiClientId}/.default`] } return pca.acquireTokenSilent(tokenRequest); } } export const msalApiFetch = async (relativeUrl: string, options: any = {}) => { if (options && options.method !== 'post') options.headers = { ...options.headers, ...{ 'pragma': 'no-cache', 'cache-control': 'no-cache' } } if (options && options.method !== 'get') options.headers = { ...options.headers, 'Content-Type': 'application/json' } const url = `${baseUrl}${relativeUrl}`; return await authService.GetToken() .then((r: AuthenticationResult) => { return { ...options, headers: { ...options.headers, 'Authorization': `Bearer ${r.accessToken}` }}}) .then((opt: any) => fetch(url, opt)) .then((response: Response) => { if (!response.ok) { response.json().then((problem: any) => { console.error(JSON.stringify(problem)) }, (error: Error) => { console.error(error) }) } return response; }); }
-
Modify index.tsx
import { MsalProvider } from "@azure/msal-react"; import { pca } from "./authConfig"; ReactDOM.render( <MsalProvider instance={pca}> <App /> </MsalProvider>, document.getElementById('root'), );
-
Modify App.tsx
import { MsalAuthenticationTemplate } from "@azure/msal-react"; import { InteractionType } from "@azure/msal-browser"; import { loginRequest } from "./authConfig" <MsalAuthenticationTemplate authenticationRequest={loginRequest} /* ensure we request all scopes outlined above */ interactionType={InteractionType.Redirect} /* avoids blocked pop-ups */ >