Wrong version of access token (expect V2 , received V1) - azure-active-directory

I am trying to register users from the Azure Active directory using #azure/msal-angular, to be more precise I tried the following tutorial
Those are the changes I have made to the project
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: 'my_real_client_id,
redirectUri: 'http://localhost:4200',
authority: 'https://login.microsoftonline.com/my_real_tenant_id',
postLogoutRedirectUri: '/'
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE, // set to true for IE 11
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false
}
}
});
}
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('https://graph.microsoft.com/v1.0/me', ['user.read']);
protectedResourceMap.set('http://localhost:5000/', ['profile']);
return {
interactionType: InteractionType.Redirect,
protectedResourceMap
};
}
The problem is that MsalInterceptor adds V1 token to the URL for the request to my API which expects V2.
Azure is configured to accessTokenAcceptedVersion: 2
I can provide more information if needed
Update
In my case, the problem was due to the scopes specified, both API for "user.read" and "profile" require V1 accessToken

Although you have resolved the issue, I would like to clarify more details.
In fact it's not the permissions "user.read" and "profile" require V1 accessToken. The actual reason is that:
Access Tokens versions are determined by the configuration of your application/API in the manifest. You have to change the accessTokenAcceptedVersion part of the manifest to 2 if you want a V2 access token. This is also the reason why you cannot control what version of the access token you will get from your client application when you request an access token.
We can't modify the accessTokenAcceptedVersion from Microsoft Graph side.
So the conclusion is that an access token for MS Graph and those are always V1 access token.

Related

Dotnet API requires auth both for application and React

I must be really stupid, But I have been struggling for weeks to try solve this issue, and all the digging I have done (in Stack overflow and MS Documentation) has yielded no results (or I'm too stupid to implement auth correctly)
I have a dotnet service which needs to act as an API - both for an application to post data to (an exe which logs exception data), and for a UI (react app) to get the posted exceptions
the exe can successfully send data to the dotnet app after first getting a token from login.microsoftonline.com and then sending the token (and secret) in the http request.
A sample postman pre-request script of the auth used (I've set all the secret stuff as environment variables):
pm.sendRequest({
url: 'https://login.microsoftonline.com/' + pm.environment.get("tenantId") + '/oauth2/v2.0/token',
method: 'POST',
header: 'Content-Type: application/x-www-form-urlencoded',
body: {
mode: 'urlencoded',
urlencoded: [
{key: "grant_type", value: "client_credentials", disabled: false},
{key: "client_id", value: pm.environment.get("clientId"), disabled: false},
{key: "client_secret", value: pm.environment.get("clientSecret"), disabled: false}, //if I don't configure a secret, and omit this, the requests fail (Azure Integration Assistant recommends that you do not configure credentials/secrets, but does not provide clear documentation as to why, or how to use a daemon api without it)
{key: "scope", value: pm.environment.get("scope"), disabled: false}
]
}
}, function (err, res) {
const token = 'Bearer ' + res.json().access_token;
pm.request.headers.add(token, "Authorization");
});
Now in React, I am using MSAL(#azure/msal-browser) in order to login a user, get their token, and pass the token to one of the dotnet endpoints using axios as my http wrapper, but no matter what I do, it returns http status 401 with WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid".
A simplified code flow to login user and request data from the API:
import {publicClientApplication} from "../../components/Auth/Microsoft";//a preconfigured instance of PublicClientApplication from #azure/msal-browser
const data = await publicClientApplication.loginPopup();
// ... some data validation
publicClientApplication.setActiveAccount(data.account);
// .. some time and other processes may happen here so we don't access token directly from loginPopup()
const activeAccout = publicClientApplication.getActiveAccount();
const token = publicClientApplication.acquireTokenSilent(activeAccount).accessToken;
const endpointData = await api()/*an instance of Axios.create() with some pre-configuration*/.get(
'/endpoint',
{ headers: {'Authorization': `bearer ${token}`} }); // returns status 401
The dotnet service has the following configurations
public void ConfigureServices(IServiceCollection services){
...
var authScheme = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
authScheme.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
...
}
namespace Controllers{
public class EndpointController : ControllerBase{
...
[Authorize]
[HttpGet]
public IActionResult GetEndpoint(){
return Ok("you finally got through");
}
}
}
I've literally tried so many things that I've lost track of what I've done...
I've even cried myself to sleep over this - but that yielded no results
i can confirm that running the request in postman, with the pre request script, it is possible to get the response from the endpoint
So....
After much digging and A-B Testing I was able to solve this issue.
I discovered that I was not sending the API scope to the OAuth token endpoint. To do this I needed to change the input for acquireTokenSilent.
The updated code flow to login user and request data from the API:
import {publicClientApplication} from "../../components/Auth/Microsoft";//a preconfigured instance of PublicClientApplication from #azure/msal-browser
const data = await publicClientApplication.loginPopup();
// ... some data validation
publicClientApplication.setActiveAccount(data.account);
// .. some time and other processes may happen here so we don't access token directly from loginPopup()
const activeAccout = publicClientApplication.getActiveAccount();
const token = publicClientApplication.acquireTokenSilent({scopes:["api://XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/.default"],account:activeAccount}).accessToken;//here scopes is an array of strings, Where I used the api URI , but you could directly use a scope name like User.Read if you had it configured
const endpointData = await api()/*an instance of Axios.create() with some pre-configuration*/.get(
'/endpoint',
{ headers: {'Authorization': `bearer ${token}`} }); // returns status 401

MS Graph API's subscription create to OnlineMessages fails

I am attempting to create a subscription to the resource /communications/onlineMeetings/?$filter=JoinWebUrl eq '{JoinWebUrl}' with the node ms graph client.
To do this, I have:
Two tenants, one with an active MS Teams license (Office 365 developer), whereas the other tenant houses my client app, which is a multi-tenant app.
Added the required scope to the client app (App level scope: OnlineMeetings.Read.All)
Given admin consent to the client app from the MS Teams tenant. The screenshot below shows the client app scope details in the MS Teams tenant.
Initialized the MSAL auth library as follows in the client app:
const authApp = new ConfidentialClientApplication({
auth: {
clientId: 'app-client-id',
clientSecret: 'app-client-secret',
authority: `https://login.microsoftonline.com/${tenantId}`,
},
});
Gotten an accessToken via the call:
const authContext = await authApp.acquireTokenByClientCredential({
authority: `https://login.microsoftonline.com/${tenantId}`,
scopes: ['https://graph.microsoft.com/.default'],
skipCache: true,
});
const accessToken = authContext.accessToken;
Initialized the MS Graph client as follows:
const client = MSClient.init({
debugLogging: true,
authProvider: (done) => {
done(null, accessToken);
},
});
Created a subscription successfully for the: CallRecords.Read.All scope (which correctly sends call record notifications to the defined webhook) with the following call:
const subscription = await client
.api('/subscriptions')
.version('beta')
.create({
changeType: 'created,updated',
notificationUrl: `https://my-ngrok-url`,
resource: '/communications/callrecords',
clientState: 'some-state',
expirationDateTime: 'date-time',
});
Attempted to create a subscription for the OnlineMeetings.Read.All scope with the following call:
const subscription = await client
.api('/subscriptions')
.version('beta')
.create({
resource: `/communications/onlineMeetings/?$filter=JoinWebUrl eq '{JoinWebUrl}'`,
changeType: 'created,updated',
notificationUrl: `https://my-ngrok-url`,
clientState: 'some-state',
expirationDateTime: 'date-time',
includeResourceData: true,
encryptionCertificate: 'serialized-cert',
encryptionCertificateId: 'cert-id',
});
This results in the error message:
GraphError: Operation: Create; Exception: [Status Code: Forbidden;
Reason: The meeting tenant does not match the token tenant.]
I am unsure what is causing this and and how to debug it further. Any help would be much appreciated.
My understanding of the joinWebUrl in the resource definition was incorrect -- the joinWebUrl is a placeholder for an actual online-meeting's URL (rather than a catch-all for all created meetings).
What I was hoping to achieve however is (currently) not possible, which is creating a subscription on a particular user's join/leave events to any online-meeting.

Setting up Azure B2C with React

I am trying to configure my react/.NET 5.0 application to work with Azure B2C. I have everything set up , I have tried to run this against an MVC application and I get the login screen. But for some reason, when I try to redirect from a react page, I keep getting the same error. There appears to be almost no real good documentation for this as well. This is my authConfig file.
export const msalConfig = {
auth: {
clientId: process.env.REACT_APP_ADB2C_CLIENT_ID
, authority: process.env.REACT_APP_ADB2C_AUTHORITY
, knownAuthorities: [process.env.REACT_APP_KNOWN_AUTH]
, clientSecret: process.env.REACT_APP_CLIENT_SECRET
, reponseType: 'code'
},
cache: {
cacheLocation: 'sessionStorage'
,storeAuthStateInCoolie: false
}
};
const b2cPolicies = {
name: {
signUpSignIn: "B2C_1_cp_signin_up"
, forgotPassword: "B2C_1_cp_forgot_pwd"
, editProfile: "B2C_1_cp_edit_profile"
},
authorities: {
signUpSignIn: {
authority: `https://${process.env.REACT_APP_TENANT_LOGIN}/${process.env.REACT_APP_TENANT}/${process.env.REACT_APP_SIGNUP_POLICY}`,
},
forgotPassword: {
authority: `https://${process.env.REACT_APP_TENANT_LOGIN}/${process.env.REACT_APP_TENANT}/${process.env.REACT_APP_FORGOT_POLICY}`,
},
editProfile: {
authority: `https://${process.env.REACT_APP_TENANT_LOGIN}/${process.env.REACT_APP_TENANT}/${process.env.REACT_APP_EDIT_POLICY}`
}
},
authorityDomain: process.env.REACT_APP_TENANT_LOGIN
}
export const loginRequest = {
scopes: ["openid", "profile"],
};
I keep running into this error when I click on the link to redirect.
Any help with this would be great.
The reply URL must begin with the scheme https. Please check if reply urls are configured correctly which must be same in azure portal and in code .
Check if the callback path is set to identity provider something like /signin-oidc for redirect url .(And make sure you have unique callback if multiple urls are used.
such as https://contoso.com/signin-oidc.
The CallbackPath is the path where server will redirect during authentication. It's automatically handled by the OIDC middleware itself, that means we can't control the logic by creating a new controller/action and set CallbackPath to it
If you have check marked id_token in portal, try redirecting to home page ,instead of api actions directly.
Change the cookies to be set as secure using this in start up class
services.Configure(options =>
{
options.CheckConsentNeeded = context => true;//add if consent needed
options.MinimumSameSitePolicy = SameSiteMode.None; // else try SameSiteMode.Lax;
options.Secure = CookieSecurePolicy.Always;
});
use Microsoft.AspNetCore.HttpOverrides; reference in startup.cs class, by including the nuget package.
Also check and Add > app.UseHttpsRedirection(); above app.authentication(); in startup configure method.
then try again.
If not try to set ProtocolMessage.RedirectUri to the HTTPS url
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAdB2C", options);
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = async ctx =>
{
/* Redirect Uri modified for https protocol */
ctx.ProtocolMessage.RedirectUri = urlWithHttps
}
}
});
Or you can pass login hint :Please refer this doc.
References:
Tutorial: Register an application - Azure AD B2C | Microsoft Docs
Configure authentication in a sample web application by using Azure Active Directory B2C | Microsoft Docs

Unable to configure ASP.NET for Azure Access Token

I was hoping that someone might be able to shed some light on issues that I'm having with authentication. I've mostly used this guidance: https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-overview
I've got a react app that is successfully (I think...) retrieving access tokens for my API:
const account = msalInstance.getActiveAccount();
if (account) {
msalInstance.acquireTokenSilent({
...apiToken,
account: account
}).then((response) => {
setToken(response.accessToken);
});
}
My requests place the token in the authorization header:
Headers
My token looks looks like: Token
API Registration
My API returns 401 whenever I use the Authorize attribute. Because my client is retrieving a token that looks correct, I'm assuming the issue is on my API. This is what I have in my startup auth:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(
new TokenValidationParameters
{
// Check if the audience is intended to be this application
ValidAudiences = new[] { [MY_API_CLIENT_ID (SAME AS AUDIENCE IN TOKEN)], [MY API REGISTRATION URI] },
// Change below to 'true' if you want this Web API to accept tokens issued to one Azure AD tenant only (single-tenant)
// Note that this is a simplification for the quickstart here. You should validate the issuer. For details,
// see https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore
ValidateIssuer = false,
ValidateAudience = false,
ValidateTokenReplay = false,
ValidateIssuerSigningKey = false,
ValidateLifetime = false,
ValidateActor = false, //all false for testing
},
new OpenIdConnectSecurityKeyProvider("https://login.microsoftonline.com/[MY_TENANT_ID]/v2.0/.well-known/openid-configuration")
),
});
Tough silent error here. I noticed that none of the validation callbacks were being invoked. I was missing the package Microsoft.Owin.Host.SystemWeb

Microsoft Graph API auth error: "Access token validation failure. Invalid audience"

Okay, so I've spent the last two days with this error, and just found a solution. In my search, I didn't find an single answer solving the issue that I had (rather I found multiple that eventually pointed me to the solution). So here's my attempt at explaining you the solution to the "Access token validation failure. Invalid audience" error:
TLDR:
Check that "https://graph.microsoft.com" is listed as AUD (audience) in the access token you receive when authenticating with MSAL on https://jwt.ms/ (Microsoft is behind the site jwt.ms source: https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens). In my case, the back-end API scope was listed, not "https://graph.microsoft.com". That's why the "audience" was invalid when Microsoft graph api checks the access token.
The solution is to request two different access tokens, one for the backend scope, and one for the https://graph.microsoft.com/User.Read scope:
/**
* Retrieve token for backend
*/
export const getToken = async (account): Promise<AuthenticationResult> => {
return await msalInstance.acquireTokenSilent({
scopes: [process.env.REACT_APP_API_SCOPE as string],
redirectUri: current_url,
account,
});
};
/**
* Retrieve token for Microsoft Graph API:
*/
export const getTokenForGraphApi = async (
account
): Promise<AuthenticationResult> => {
return await msalInstance.acquireTokenSilent({
scopes: ["https://graph.microsoft.com/User.Read"],
redirectUri: current_url,
account,
});
};
Here's the long story of how I found out:
I wanted to be able to query the Microsoft Graph API from a React application.
I've had the admin of my organization set up the Azure Portal, so that our App registration has API Permissions:
Backend API permission
Microsoft Graph
"User.Read"
"User.ReadBasic.All".
In React when I authenticate, I've used scopes:
scopes: [
process.env.REACT_APP_API_SCOPE as string,
"User.Read",
],
The authentication goes well, and I get an access token.
The access token works with our backend API, however when I try to use the access token with the Microsoft Graph API, I get the error:
"Access token validation failure. Invalid audience".
I read and searched forums, and I tried using jwt.ms.
Only our API is listed as "aud", and hence I suspect I need a token where both our API and "https://graph.microsoft.com" is placed.
I then tried preceding my User.Read scope with "https://graph.microsoft.com" so it would be:
scopes: [
process.env.REACT_APP_API_SCOPE as string,
"https://graph.microsoft.com/User.Read"
],
But it failed to authenticate with the error message:
"AADSTS28000: Provided value for the input parameter scope is not valid because it contains more than one resource. Scope api://{API-application-id}/a-scope https://graph.microsoft.com/User.Read openid profile is not valid."
Here, our backend is one resource, which has a-scope, and "https://graph.microsoft.com" is another resource with scope "User.Read".
The solution is hence to require two seperate access tokens: One with scope "https://graph.microsoft.com/User.Read", that you can use with the graph api, and another access token for your backend:
/**
* Retrieve token for backend
*/
export const getToken = async (account): Promise<AuthenticationResult> => {
return await msalInstance.acquireTokenSilent({
scopes: [process.env.REACT_APP_API_SCOPE as string],
redirectUri: current_url,
account,
});
};
/**
* Retrieve token for Microsoft Graph API:
*/
export const getTokenForGraphApi = async (
account
): Promise<AuthenticationResult> => {
return await msalInstance.acquireTokenSilent({
scopes: ["https://graph.microsoft.com/User.Read"],
redirectUri: current_url,
account,
});
};
The error message: "Access token validation failure. Invalid audience" tells you that the "AUD" (audience) is incorrectly set on the access token you are using with the Microsoft Graph API.
You can check your token with https://jwt.ms/ by pasting in the access token.
(Microsoft is behind the site jwt.ms site: https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens)
The solution is hence to require two separate access tokens: One with scope "https://graph.microsoft.com/User.Read", that you can use with the Microsoft graph api, and another access token for your backend:
/**
* Retrieve token for backend
*/
export const getToken = async (account): Promise<AuthenticationResult> => {
return await msalInstance.acquireTokenSilent({
scopes: [process.env.REACT_APP_API_SCOPE as string],
redirectUri: current_url,
account,
});
};
/**
* Retrieve token for Microsoft Graph API:
*/
export const getTokenForGraphApi = async (
account
): Promise<AuthenticationResult> => {
return await msalInstance.acquireTokenSilent({
scopes: ["https://graph.microsoft.com/User.Read"],
redirectUri: current_url,
account,
});
};
Then the AUD (audience) will be set correctly on both access tokens.

Resources