msal (in another file. Passed using MsalProvider
):
const msalInstance = new PublicClientApplication({
auth: {
clientId: <B2C-Application-ID>,
authority: "https://login.microsoftonline.com/<tenant-directory-id>",
redirectUri: "http://localhost:3000",
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
}
});
Import:
import * as msal from "@azure/msal-browser";
import {EventType, InteractionStatus} from "@azure/msal-browser";
import React, {createContext, FC, useState} from "react";
import {useIsAuthenticated, useMsal} from "@azure/msal-react";
import {AuthenticationContextType} from "../@types/authentication";
import {EndSessionRequest} from "@azure/msal-browser/dist/request/EndSessionRequest";
import jwtDecode, {JwtPayload} from "jwt-decode";
Variables:
const {instance, accounts, inProgress} = useMsal();
const isAuthenticated = useIsAuthenticated();
const [token, setToken] = useState<string | null>(null);
Login:
function loginRedirect() {
instance.loginRedirect({
scopes: ["User.Read"],
prompt: "select_account"
});
}
Acquire token:
function getToken(): string | null {
if (token) {
const decodedJwt = jwtDecode<JwtPayload>(token);
if (decodedJwt.exp && decodedJwt.exp * 1000 > Date.now()) {
return token; // Token is still valid
}
}
// If token is not available or not valid anymore, acquire a new one
if (instance.getActiveAccount() && inProgress === InteractionStatus.None) {
const accessTokenRequest = {
scopes: ["User.Read"],
account: accounts[0]
}
instance.acquireTokenSilent(accessTokenRequest)
.then(response => {
console.log(`access token: ${response.accessToken}`);
console.log(`id token: ${response.idToken}`);
setToken(response.accessToken);
return response.accessToken;
})
.catch(err => {
if (err instanceof msal.InteractionRequiredAuthError) {
return instance.acquireTokenPopup(loginRequest)
.then(response => {
setToken(response.accessToken);
return response.accessToken;
})
.catch(err => {
console.log(err);
})
} else {
console.log(err);
}
})
} else {
console.error("No account logged in to acquire token");
}
return null;
}
I acquire two tokens (ID and access) from msal (see console logs). The ID token is being validated successfully (on my API and jwt.io) but my access token is not (neither on my API nor jwt.io). Referring to this microsoft documentation I should use the access token to validate against an API.
As far as I can see, jwt.io does fetch the public key correctly from https://sts.windows.net/<tenant-directory-id>/discovery/v2.0/keys
. This means this solution is either outdated, or doesn't solve my problem. To go sure I also tried to copy&paste the public key, which didn't work either.
I also found this solution which didn't work for me either. Changing the scopes
leads to an endless login loop.
"@azure/msal-browser": "^2.28.3",
"@azure/msal-react": "^1.4.7",
"jwt-decode": "^3.1.2",
For requesting B2C access tokens you have to specify a valid scope. These are also set in Azure (Azure AD B2C -> App registrations -> your application
-> Manage -> API permissions). There you have to specify a scope. While acquiring the tokens you have to specify these scopes like this:
const accessTokenRequest = {
scopes: ["https://<tenant-name>.onmicrosoft.com/<app-id>/<scope>"],
}
await instance.acquireTokenSilent(accessTokenRequest)
.then(response => {
setIdToken(response.idToken);
setAccessToken(response.accessToken);
})
.catch(async err => {
if (err instanceof msal.InteractionRequiredAuthError) {
await instance.acquireTokenPopup(accessTokenRequest)
.then(response => {
setIdToken(response.idToken);
setAccessToken(response.accessToken);
})
.catch(err => {
console.log(err);
})
} else {
console.log(err);
}
})
tenant-name
you can find this in the Application ID URI
app-id
is your Application (client) ID
your-scope
could be something like Subscriptions.Read
A full example for a scope could be:
https://mydemo.onmicrosoft.com/12345678-0000-0000-0000-000000000000/Subscriptions.Read
For me the problem was 1. Scope
but maybe this does not solve the problem for others. Here is something else to try:
Following this article, the sts
url is used vor the v1 endpoint. The documentation claims:
The endpoint used, v1.0 or v2.0, is chosen by the client and only impacts the version of id_tokens. Possible values for
accesstokenAcceptedVersion
are 1, 2, or null. If the value is null, this parameter defaults to 1, which corresponds to the v1.0 endpoint.
This means that the used endpoint (v2.0 in my case) affected only the id-token, which made it validate successfully. The access token was still v1 thus with no validated signature.
To change the version, accessTokenAcceptedVersion
needs to be set to 2 inside the Manifest. It is located at portal.azure.com -> Azure AD B2C -> App registrations -> your application
-> Manage -> Manifest:
{
...
"accessTokenAcceptedVersion": 2,
...
}
Save the changes and wait. For me it took several hours to wait for the change to be applied. And I had to apply solution 1. Scope
as well. After that, the iss
of new tokens should be https://login.microsoftonline.com/<tenant-directory-id>/v2.0
instead of the sts
-uri