How to validate Token from personal Microsoft Account? - azure

I created a API that I try to protect with AAD.
It's already working for Accounts from my organization and Accounts from other organizations but not for personal Microsoft Accounts.
I already tryed different Endpoints but I think the common Endpoint should be the correct Endpoint if I want any Account to be able to sign in.
This is how my API Startup looks:
services.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Authority = "https://login.microsoftonline.com/common";
o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
// Both App ID URI and client id are valid audiences in the access token
ValidAudiences = new List<string>
{
"APP ID",
},
ValidateIssuer = false,
};
});
And this is how I get the accesstoken at javascript:
var applicationConfig = { //Cloudlist API via TestApp
clientID: "APP ID",
authority: "https://login.microsoftonline.com/common",
graphScopes: ["https://hsde.onmicrosoft.com/APP ID/User"]
};
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID,
applicationConfig.authority, null, { storeAuthStateInCookie: true,
cacheLocation: "localStorage" });
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callAPI(accessToken);;
});
}, function (error) {
console.log(error);
});
When I sign in with a personal Microsoft Account and use the accessToken to call the API I get a 401 Unauthorized Error.
The response header says:
www-authenticate: Bearer error="invalid_token", error_description="The signature key was not found"
Is there anything I have to do differently when signing in with a personal Microsoft Account ?

First, get a token and try to decoded in jwt.io just to check if audience id is the same that you are using in you web api.

Related

AuthenticationFailed when authenticating via nodejs app and package #azure/msal-node

I have an Azure app registered . I am trying to authenticate to that app . I am able to do that and successfully get the accesstoken and idtoken.
However, when I use that token and try to make a request to list subscriptions API (https://management.azure.com/subscriptions?api-version=2020-01-01) , the request fails and give response "AuthenticationFailed". I have also tried changing the scope to https://management.azure.com/.default but the same error is there. Below is the nodejs code and I am also attaching the API permissions of app
const config = {
auth: {
clientId: 'xxx',
authority: 'https://login.microsoftonline.com/organizations',
clientSecret: 'yyy',
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
},
},
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
app.get('/', (req, res) => {
const authCodeUrlParameters = {
scopes: ['user.read','https://management.azure.com/user_impersonation'],
redirectUri: REDIRECT_URI,
};
// get url to sign user in and consent to scopes needed for application
pca
.getAuthCodeUrl(authCodeUrlParameters)
.then((response) => {
res.redirect(response);
})
.catch((error) => console.log(JSON.stringify(error)));
});
The response I am getting is
{
"error": {
"code": "AuthenticationFailed",
"message": "Authentication failed."
}
}
The error "AuthenticationFailed" usually occurs if you are using different scope token to call the API.
I tried to generate access token with the same scope as you
via Postman and got the same error while calling the query like below:
Please note that,
user.read audience is Microsoft Graph API
https://management.azure.com/user_impersonation audience is Azure Service Management.
As you have given two different scopes with different audiences, it will consider the first scope (user.read) to generate the token as mentioned in this SO Thread which was solved by me.
When you call the query https://management.azure.com/subscriptions?api-version=2020-01-01 with the above token, you will get the error as it is intended for MS Graph audience.
I tried to generate the token with scope https://management.azure.com/user_impersonation only, removing user.read like below:
With the above generated token, I am able to call the API successfully like below:
If you want token with different scopes, then you have to generate two access tokens separately.

(msal-node) - Dynamics 365 Business Central returns 401 for any resource with received token

We are currently switching to an OAuth based authorization solution based on the #azure/msal-node package to authorize our API's with the ones provided by Dynamics 365 Business Central (19.5, Cloud). Somehow I can not get it to work. Our tenant instance just returns a 401 when requesting any resource using the retrieved token.
Really reached a dead end here, any help would be greatly appreciated.
Permissions set in Azure's "App Registrations" section
And yes, I have consented all of those Permissions on behalf of our tenant (admin consent)
API.ReadWrite.All
app_access
Automation.ReadWrite.All
The token even contains following scopes when decoded over at jwt.ms:
{
"scopes": [
"Automation.ReadWrite.All",
"app_access",
"API.ReadWrite.All"
]
}
Example of our implementation
/**
* Uses `msal-node` to authenticate against the microsoft servers
* to gain access to Dynamics 365 Business Central
*/
async getClientCredentialsToken() {
try {
const validity = await this.validateClientCredentialsToken();
if (validity) return;
const authOptions = {
clientId: process.env.AAD_CLIENT_ID,
authority: process.env.AAD_AUTHORITY,
clientSecret: process.env.AAD_CLIENT_SECRET,
};
const cacheOptions = {
Account: {},
IdToken: {},
AccessToken: {},
RefreshToken: {},
AppMetadata: {},
};
const cca = new msal.ConfidentialClientApplication({
cache: cacheOptions,
auth: authOptions,
});
const response = await cca.acquireTokenByClientCredential({
azureRegion: null,
skipCache: true,
scopes: ["https://api.businesscentral.dynamics.com/.default"],
});
return response;
} catch (err) {
console.log(err);
}
},
Example of a response when requesting an existing endpoint
No matter what endpoint is hit. This is the response I get for it:
{
"error": {
"code": "Authentication_InvalidCredentials",
"message": "The server has rejected the client credentials. CorrelationId: b004d293-f576-40c9-bbc6-3fb32533a65b."
}
}
Solution found inside the Docs
So for anybody stumbling upon the same scenario I have found something that finally worked! You need to register the application inside the Dynamics 365 Business Central client as described inside the official documentation.

Retrieving AAD OpenID auth 'access_token' within a.NET Core 3.1 Web App

I'm trying to get OpenID connect auth to put the access_token into a cookie so I can use the access token to send an e-mail on behalf of the user using their Office365 account and Microsoft.Graph API.
Relevant lines from the Startup.Configure method:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Login";
})
.AddAzureAD(options =>
{
Configuration.Bind("AzureAD", options);
});
services.Configure<OpenIdConnectOptions(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority += "/v2.0/";
options.ResponseType = "code id_token";
options.SaveTokens = true;
});
Side Note
My OpenIdConnectOptions actually includes a few more things which I've omitted here, for example:
options.Scope.Add("offline_access");
options.Scope.Add("email");
options.Scope.Add("Mail.Send");
After the user has logged into their Office365 account account, they're redirected back into the app, however, when I invoke var token = await HttpContext.GetTokenAsync("access_token"); null is returned.
I've spent hours getting nowhere with this, Any ideas?

Returning email address as part of AAD B2C msal.js code

How do I correctly configure and code a call to AAD B2C using msal.js that returns email address in the response?
Background
I'm looking to write a javascript integration into Shiny, the R dashboard solution, which needs JavaScript integrations of authentication solutions. The dashboard must authenticate against Azure Active Directory B2C. Shiny essentially works as a SPA application.
AAD B2C config
I have an AAD B2C user flow:
name: B2C_1_signup_signin
identity providers: email signup
user attributes: email address
application claims:
email addresses
identity provider
I have an AAD B2C Application:
name: bigdashboard
app id: a0cfc440-c766-43db-9ea8-40a1efbe22ac
include web app / web api: yes
allow implicit flow: yes
app id uri: https://lduceademo.onmicrosoft.com/big
include native client: no
api access:
Access the user's profile: (All available options selected)
Acquire an id_token for users (openid)
Acquire a refresh_token for users (offline access)
bigdashboard:
read (read)
Access this app on behalf of the signed-in user (user_impersonation)
published scopes:
read
user_impersonation
Additionally, I've used the App Registrations (preview) to add some api permissions for Microsoft Graph and all have been granted admin consent.
Microsoft Graph:
User.Read
email
offline_access
openid
profile
Current JavaScript
Code amended from the following samples:
AAD B2C JS MSAL SPA
The MSAL.js lib v1.1.3 is being used to support the below bespoke code.
// The current application coordinates were pre-registered in a B2C tenant.
var appConfig = {
b2cScopes: ["profile","email","openid", "https://lduceademo.onmicrosoft.com/big/read"]
};
// configuration to initialize msal
const msalConfig = {
auth: {
clientId: "a0cfc440-c766-43db-9ea8-40a1efbe22ac", //This is your client ID
authority: "https://lduceademo.b2clogin.com/lduceademo.onmicrosoft.com/B2C_1_signup_signin", //This is your tenant info
validateAuthority: false
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
// instantiate MSAL
const myMSALObj = new Msal.UserAgentApplication(msalConfig);
// request to signin - returns an idToken
const loginRequest = {
scopes: appConfig.b2cScopes
};
// request to acquire a token for resource access
const tokenRequest = {
scopes: appConfig.b2cScopes
};
// signin and acquire a token silently with POPUP flow. Fall back in case of failure with silent acquisition to popup
function signIn() {
myMSALObj.loginPopup(loginRequest).then(function (loginResponse) {
getToken(tokenRequest).then(updateUI);
}).catch(function (error) {
console.log(error);
});
}
//acquire a token silently
function getToken(tokenRequest) {
return myMSALObj.acquireTokenSilent(tokenRequest).catch(function(error) {
console.log("aquire token popup");
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenPopup(tokenRequest).then(function (tokenResponse) {
}).catch(function(error){
console.log("Failed token acquisition", error);
});
});
}
// updates the UI post login/token acqusition
function updateUI() {
const userName = myMSALObj.getAccount().name;
console.log(myMSALObj.getAccount());
console.log("User '" + userName + "' logged-in");
$('.signin').toggleClass('hidden', true);
$('.signout').toggleClass('hidden', false);
Shiny.setInputValue('message', userName);
}
// signout the user
function logout() {
// Removes all sessions, need to call AAD endpoint to do full logout
myMSALObj.logout();
}
Current response
From this I get back an Account object that shows up in the console like:
accountIdentifier: "ddc90829-f331-4214-8df1-0cf6052f4b61"
environment: "https://lduceademo.b2clogin.com/c1138a05-4442-4003-afc7-708629f4554c/v2.0/"
homeAccountIdentifier: "ZGRjOTA4MjktZjMzMS00MjE0LThkZjEtMGNmNjA1MmY0YjYxLWIyY18xX3NpZ251cF9zaWduaW4=.YzExMzhhMDUtNDQ0Mi00MDAzLWFmYzctNzA4NjI5ZjQ1NTRj"
idToken:
aud: "a0cfc440-c766-43db-9ea8-40a1efbe22ac"
auth_time: 1575368495
exp: 1575372095
iat: 1575368495
iss: "https://lduceademo.b2clogin.com/c1138a05-4442-4003-afc7-708629f4554c/v2.0/"
nbf: 1575368495
nonce: "0933fc11-e24f-4ce2-95e2-0afe9bcc1d72"
sub: "ddc90829-f331-4214-8df1-0cf6052f4b61"
tfp: "B2C_1_signup_signin"
ver: "1.0"
name: undefined
sid: undefined
userName: undefined
Partly, my test account wasn't great - it was a native ie didn't sign up AAD B2C account, but querying email address should be performed like:
function updateUI() {
// query the account
const account = msal.getAccount();
// first email address
const username = account.idTokenClaims.emails[0];
// situation specific code
$('.signin').toggleClass('hidden', true);
$('.signout').toggleClass('hidden', false);
Shiny.setInputValue('message', username);
}

Firestore Custom Tokens

I am trying to use a custom auth token with firestore. I am using nodejs to generate the token with the following code.
const admin = require('firebase-admin');
const serviceAccount = require('./ServiceAccountKey.json')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
var uid = "some-uid";
var claim = {
control: true
};
admin.auth().createCustomToken(uid, true)
.then(function(customToken) {
console.log(customToken)
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
When I run it I get a token. I take that token and try it out using
https://firestore.googleapis.com/v1beta1/projects/example-project-5caa9/databases/(default)/documents/users with the headers
Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiY29udHJvbCI6dHJ1ZX0sInVpZCI6InNvbWUtdWlkIiwiaWF0IjoxNTI4MTQ0NzY3LCJleHAiOjE1MjgxNDgzNjcsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHl0b29sa2l0Lmdvb2dsZWFwaXMuY29tL2dvb2dsZS5pZGVudGl0eS5pZGVudGl0eXRvb2xraXQudjEuSWRlbnRpdHlUb29sa2l0IiwiaXNzIjoiZmlyZWJhc2UtYWRtaW5zZGsteG9jMDRAZXhhbXBsZS1wcm9qZWN0LTVjYWE5LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic3ViIjoiZmlyZWJhc2UtYWRtaW5zZGsteG9jMDRAZXhhbXBsZS1wcm9qZWN0LTVjYWE5LmlhbS5nc2VydmljZWFjY291bnQuY29tIn0.Bjl6VY5CZKIpNyCayROWr_ZBSRmo11hiwtnx_cbbw2Ggk3J2x0Ml2OkpXhU-vAD6Q53fCZwGgXeCdxnsXw0lr55cJH3Q6J7gitzQoRnfJgUX9Dv1gbI90OWashxMmxtzPIpwgSnfBv61mkdv9ZVrF8o362mQBx_LUQzvGgVPEN9_9UNCH7peOS4KYr_YRMpCQVem0XMNh9WKlyBZuScjHpY6dZZhXqOHda0W9-MNAfvQ-D0pt-osq4ty-D_WYk6CjLNmxzvHoZeoIk1YShJM4Mpyec3lXFcCXNYG2c3_r2tskTB0LF7Fc7Bg5XuJwlrAzHrnRis6iZFCx8sqH1b-Zg
get the following JSON.
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
My rules are as follow
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
You can't directly access REST APIs with a custom token. You need to sign in using the custom token, and obtain an ID token. There are 2 ways to do this:
Sign in from a Firebase Client SDK
Use the Firebase Auth REST API to exchange the custom token to an ID token.
Then you can access the Firestore REST API with the resulting ID token:
https://firebase.google.com/docs/firestore/use-rest-api#working_with_firebase_id_tokens

Resources