We have an Azure AD B2C instance which is configured with 2 providers:
AAD
custom OIDC
I am using this sample SPA: https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp
That sample has following login procedure:
function login() {
clientApplication.loginPopup(applicationConfig.b2cScopes).then(function (idToken) {
clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function (accessToken) {
updateUI();
}, function (error) {
clientApplication.acquireTokenPopup(applicationConfig.b2cScopes).then(function (accessToken) {
updateUI();
}, function (error) {
logMessage("Error acquiring the popup:\n" + error);
});
})
}, function (error) {
logMessage("Error during login:\n" + error);
});
}
This is what can happen in the application:
User presses the login button. The login pop-up will be shown.
User logs in with his AAD account.
Application tries to acquire token silently for the current AAD
user. This fails and a pop-up window will be shown.
User logs in with custom OIDC account.
In this situation the AAD user is logged in (id_token) but the access_token represents the custom OIDC user.
I have tried
clientApplication.acquireTokenPopup(
applicationConfig.b2cScopes,
applicationConfig.authority,
clientApplication.getUser(),
{ 'prompt': 'none' }
).then(...);
but did not worked. It presents the list of the providers to choose from.
How can I achieve that acquireTokenPopup will redirect me to AAD without home realm discovery/provider discovery?
Try passing in the parameter "domain_hint" and set the value to the value that you provided in the AAD technical profile for
Related
I am trying to do a microsoft sso, where any user can use his microsoft account to sign in to the app. Now, if the user is part of the application users it works perfectly fine, but when I try to log in with my personal one, it gives me an error message saying "The account does not exist in this organization".
My endpoint for the sso:
const REDIRECT_URI = "http://localhost:3000/redirect";
const cca = new msal.ConfidentialClientApplication(config);
const scopes = ["user.read"];
router.get("/auth", (req, res) => {
// Construct a request object for auth code
const authCodeUrlParameters = {
scopes: scopes,
redirectUri: REDIRECT_URI,
};
// Request auth code, then redirect
cca
.getAuthCodeUrl(authCodeUrlParameters)
.then((response) => {
return res.send(response);
})
.catch((error) => res.send(error));
});
router.get("/redirect", (req, res) => {
// Use the auth code in redirect request to construct
// a token request object
const tokenRequest = {
code: `${req.query.code}`,
scopes: scopes,
redirectUri: REDIRECT_URI,
};
// Exchange the auth code for tokens
cca
.acquireTokenByCode(tokenRequest)
.then(async (response) => {
res.send(response);
})
.catch((error) => res.status(500).send(error));
});
It gives me an error message saying, "The account does not exist in this organization".
The error message shows that your personal Microsoft account is not associated with the organization that the application is using for authentication. Microsoft provides two different authentication services: Microsoft Account (MSA) and Azure Active Directory (AAD).
By using Guest method, you can use your personal account by assigning the roles.
If your organization has set up the application to use AAD for authentication, then you need to have an AAD account in the same organization in order to log in.
Create an AAD account and map it with the same email address as your MSA. This will allow you to log into the application using your AAD account.
And if you don't want to create an AAD account, you can use a different email address (such as a work email address) that is already associated with the organization's AAD.
Steps to create Microsoft SSO
Register your application to access Microsoft Graph API, and you need to first register your application in the Microsoft Developer Portal and obtain a client ID and secret.
The first step in the authorization code grant flow is to redirect the user to the Microsoft authorization endpoint. The user must sign in and grant the permissions that your application requests.
After the user grants permission, Microsoft will redirect the user back to your application with an authorization code.
Your application can then exchange this code for an access token by making a POST request to the Microsoft token endpoint.
Once you have an access token, you can use it to call the Microsoft Graph API on behalf of the user. To do this, you'll need to add the access token to the Authorization header of your API request.
Access tokens are short-lived and need to be refreshed after a certain time period. You can use the refresh token to obtain a new access token without requiring the user to sign in again.
async function getAccessToken(code) {
const tokenRequestBody = querystring.stringify({
grant_type: 'authorization_code',
code: code,
redirect_uri: redirectUri,
client_id: clientId,
client_secret: clientSecret,
});
const tokenResponse = await axios.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', tokenRequestBody);
return tokenResponse.data.access_token;
}
async function getUserProfile(accessToken) {
const userProfileResponse = await axios.get('https://graph.microsoft.com/v1.0/me', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return userProfileResponse.data;
}
async function main() {
const code = 'AUTHORIZATION_CODE';
const accessToken = await getAccessToken(code);
const userProfile = await getUserProfile(accessToken);
console.log(userProfile);
}
References taken from
Nodejs-webapp-msal
ADD
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);
}
I use msal.js in vue.js project to login user through azure b2c.
Everything is fine in local server, but when I change redirect_uri and publish the website online, after the user enters the auth data in azure login form, azure redirects to {redirect_uri}/null.
Sometimes it works correctly, but many times it doesn't.
Here my code
import * as Msal from 'msal';
app = new Msal.UserAgentApplication(
{
auth:
{ clientId: this.applicationConfig.clientID,
authority: this.applicationConfig.authority,
validateAuthority: this.applicationConfig.validateAuthority,
redirectUri: this.applicationConfig.redirectUri }
});
app.loginPopup(this.tokenRequest).then(
loginResponse => {
resolve(loginResponse);
},
error => {
resolve(false);
}
);
I have configured azure AD service and an user in it and used react-adal to get the token, which worked fine.But now i need to change this flow and instead have my own login form and send the credentials to node.js express server and verify these from azure ad without a login popup from azure and store the returned token in the passport session.I have tried using node-adal but not sure how this can be achieved ,Can this be done? Are there any examples for this.Thanks
Agree with #juunas, here is the non-interactive method(login with username and password) for your reference. This sample is used to manage MicrosoftGraph Resources.
var msRestAzure = require('ms-rest-azure');
var graphRbacManagementClient = require('azure-graph');
var tenantId='abcd-efgh-ijk-lmno-12345';
// Enter your tenant ID here which can be found from your Azure AD URL
// Eg. https://manage.windowsazure.com/example.com#Workspaces/ActiveDirectoryExtension/Directory/<TenantId>/users
msRestAzure.loginWithUsernamePassword('username#contosocorp.onmicrosoft.com', 'your-password', { tokenAudience: 'graph', domain: tenantId }, function (err, credentials, subscriptions) {
if (err) console.log(err);
var client = new graphRbacManagementClient(credentials, tenantId);
var userParams = {
accountEnabled: true,
userPrincipalName: 'OfficialStark#<yourdomain.com>', //please add your domain over here
displayName: 'Jon Snow',
mailNickname: 'OfficialStark',
passwordProfile: {
password: 'WinterisComing!',
forceChangePasswordNextLogin: false
}
};
client.users.create(userParams, function (err, user, request, response) {
if (err) return console.log(err);
console.log(user);
var userObjectId = user.objectId;
client.users.list(function (err, result, request, response) {
if (err) return console.log(err);
console.log(result);
client.users.deleteMethod(userObjectId, function (err, result, request, response) {
if (err) return console.log(err);
console.log(result);
});
});
});
});
There is a way, but you should not use it.
When using federated authentication, you should not be handling passwords.
The one way that works is a legacy migration path, named the ROPC flow.
However, none of these will work:
User with MFA
User synced from on-prem AD
User with expired password
If you want to do something as the user in the background, have them login and store their refresh token securely.
You'd need to exchange the front-end access token for a back-end access token + refresh token.
Then you can use the refresh token to get new tokens for the user whenever.
Alternatively you can require application permissions to APIs and access them using client credentials from your API (client id + secret/certificate).
I made a chat bot which I want to authorize user using Azure ad b2c using OIDCStrategy. In the console, it always logs authentication failed due to: In collectInfoFromReq: policy is missing. The policy has been set in Azure. And I can't find somewhere to declare the policy in the code. Here is my server:
server.get('/login',
passport.authenticate('azuread-openidconnect',
{
failureRedirect:'/fail'
}),
function(req,res,next){
console.log('Login was called');
res.redirect('/',next);
}
)
server.post('/api/auth', passport.authenticate('azuread-openidconnect'));
And connection to the azure ad b2c:
passport.use(new OIDCStrategy({
redirectUrl:'http://localhost:3978/api/auth',
allowHttpForRedirectUrl:true,
clientID:'5fe844d7-e4d1-4c4c-ba70-078297b00abc',
clientSecret:'?aTvTEbwcNfUF2,^',
identityMetadata: 'https://login.microsoftonline.com/nuffieldbot.onmicrosoft.com/v2.0/.well-known/openid-configuration',
skipUserProfile: true,
responseType: 'code',
responseMode: 'form_post',
isB2C:true,
scope:['email','profile','offline_access','https://outlook.office.com/mail/read'],
loggingLevel:'info',
tenantName:'nuffieldbot.onmicrosoft.com',
passReqToCallback:true
},function(req, iss, sub, profile, accessToken, refreshToken, done){
log.info('Example:Email address we received was:', profile.email);
process.nextTick(function(){
findByEmail(profile.email,function(err,user){
if (err) {
return done(err);
}
if (!user){
users.push(profile);
return done(null, profile);
}
return done(null, user);
})
})
}
));
Where can I declare this policy in my code?
For B2C, we must have policy , if you have not set policy name in query string of request , it will throw error : In collectInfoFromReq: policy is missing . Please refer to source code of oidcstrategy.js :
// for B2C, we must have policy
if (self._options.isB2C && !params.policy)
return next(new Error('In collectInfoFromReq: policy is missing'));
You must set policy in request :
Sign In
You could also refer to code sample : Azure Active Directory OIDC Web Sample