how to retrieve the msal object from cache location : local storage? - node.js

First things first, I am below a novice level when it comes to Nodejs/msal/azure b2c and I am trying to understand the flow.
I started off with this sample here: https://azure.microsoft.com/en-us/resources/samples/active-directory-b2c-javascript-msal-singlepageapp/
I am using msal.js with azure ad b2c in a Nodejs application. I am redirecting the user after signing in via sign-in policy to a different page where I have my other policies.
//index.html
var clientApplication = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, authCallback, { logger: logger, cacheLocation: 'localStorage' });
function authCallback(errorDesc, token, error, tokenType) {
if (token) {
logMessage(token + ":" + token);
}
else {
logMessage(error + ":" + errorDesc);
}
}
This is my onclick login function in the index.html. The 'test(accessToken)' method does the redirection to the backend node js routes where I store the accesstoken in a session variable and the method renders to a different page(test.ejs) where my other b2c policies are stored.
function login() {
clientApplication.loginPopup(applicationConfig.b2cScopes).then(function (idToken) {
clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function (accessToken) {
test(accessToken);
}, 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);
});
}
Now my question is how can I retrieve the current state of the clientApplication Msal.UserAgentApplication object in my other view(test.ejs) to do something like this :
clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function (accessToken) {
logMessage(accessToken);
}

Related

Angular/NodeJS share frontend access token to fetch data from MS graph by backend

I have a small MEAN stack running (Angular in Frontend and NodeJS in Backend). The Frontend is protected by MSAL (#azure/msal-angular).
This part is working fine. The user gets authorized for the frontend and Angular is able to request data from MS Graph (the msal interceptor adds the token to all requests to the MS Graph and the backend):
app.module.ts
MSalModule.forRoot( new PublicClientApplication({ // MSAL Configuration
auth: {
clientId: environment.aad_client_id,
authority: 'https://login.microsoftonline.com/' + environment.aad_tenant_id + '/',
redirectUri: window.location.origin,
},
cache: {
cacheLocation : BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE,
}
}), {
// MSAL Guard Configuration
interactionType: InteractionType.Redirect,
authRequest: {
scopes: ['user.read', environment.aad_scope_api]
}
}, {
// MSAL Interceptor Configuration
interactionType: InteractionType.Redirect,
protectedResourceMap: new Map([
['https://graph.microsoft.com/v1.0', ['user.read']],
[environment.apiUrl, [environment.aad_scope_api]],
])
})
After redirect from MS login I send a post request to my NodeJS Backend to establish a session.
The login route of the Backend should extract the token from the header, and send some request to the graph, to store the user details from there in the user session.
login.js
router.post('/login', (req, res) => {
if (req.session.user) {
res.json(req.session.user);
} else {
fetchUser(req, mongodb).then(result => {
req.session.user = result;
res.json(result);
}).catch(err => {
res.status(401).json(err);
})
}
});
...
async function fetchUser(token) {
try {
const token = req.headers.authorization;
request({
headers: { 'Authorization': token },
uri: 'https://graph.microsoft.com/v1.0/me',
method: 'GET'
}, { json: true }, (err, res, body) => {
if (err) { throw err; }
const obj = ...do some things
return obj;
});
} catch(err) {
throw err;
}
}
The issue is, that the token is only valid from Frontend. MS recommend the on-behalf-of-flow for that, but I'm not able to find any way to solve this. So how can I request a new token for my backend?
You can request a token for the backend to access Graph using the client credentials authentication, and set the scopes for Graph as Application Permissions on the App Registration, such as User.Read.All.
You would instead read the "oid" from the AAD access token passed from frontend to backend for discovering the user for formatting requests to Graph. Microsoft created a tutorial on implementing which you may find helpful.

Get Token for Azure ADv2 Rest Api Protected

I create an WebApi using visual studio and the wizard, for a protected api.
The result was an new application in the Azure Portal, and a configuration file jsonconfig (I am using netcore 2.2)
The web api is very simple a part of the code is
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
Now I am trying to get a token to call the api, using the next code
var msalConfig = {
auth: {
clientId: '83a8a6ee-afd5-41d3-92bd-2a6352cff7da', //This is your client ID
authority: "https://login.microsoftonline.com/d7124a8f-3301-4c72-9231-4bb39d8b95a3" //This is your tenant info
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
and the calling
var requestObj2 = {
scopes:["https://xxxxxx.com/Test2019/user_impersonation"]
};
var myMSALObj = new Msal.UserAgentApplication(msalConfig);
function signIn() {
myMSALObj.loginPopup(requestObj).then(function (loginResponse) {
//Successful login
showWelcomeMessage();
//Call MS Graph using the token in the response
acquireTokenPopupAndCallMSGraph();
}).catch(function (error) {
//Please check the console for errors
console.log(error);
});
}
and at the end
function acquireTokenPopupAndCallMSGraph() {
//Always start with acquireTokenSilent to obtain a token in the signed in user from cache
myMSALObj.acquireTokenSilent(requestObj2).then(function (tokenResponse) {
console.log(tokenResponse.accessToken);
alert('autenticado');
callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
// Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
// Call acquireTokenPopup(popup window)
if (requiresInteraction(error.errorCode)) {
myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {
callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
});
}
});
}
Everything works but the token generated when used in postman in the Authorization Header ="Bearer Token"
Does not work.
Please any advice on how to get the token.. :(
Thanks!
The way you get access token is correct. Replace the value of scopes by scopes:["83a8a6ee-afd5-41d3-92bd-2a6352cff7da/.default"] and try again.
If this still doesn't work, paste the error message here.
Here is a complete example regarding calling the backend web api using access token.

OAuth authentication with Azure Active Directory app

I am trying to run the OAuth example in the botbuilder nodejs documentation at https://github.com/Microsoft/BotBuilder/blob/master/Node/examples/basics-oauth/app.js
I have set up the Azure Active Directory v1 application on Azure with graph api access and have added the OAuth connection to my bot. The connectionName to be used in the code below is ready with me.
But when I run the code which is taken as is from the documentation (the github link provided above), I am getting:
TypeError: connector.getUserToken is not a function
I have run this both on emulator and on webchat channel and getting the same error.
// Create your bot with a function to receive messages from the user
var bot = new builder.UniversalBot(connector, function (session) {
if (session.message.text == 'signout') {
// It is important to have a SignOut intent
connector.signOutUser(session.message.address, connectionName, (err, result) => {
if (!err) {
session.send('You are signed out.');
} else {
session.send('There was a problem signing you out.');
}
});
} else {
// First check whether the Azure Bot Service already has a token for this user
connector.getUserToken(session.message.address, connectionName, undefined, (err, result) => {
if (result) {
// If there is already a token, the bot can use it directly
session.send('You are already signed in with token: ' + result.token);
} else {
// If there not is already a token, the bot can send an OAuthCard to have the user log in
if (!session.userData.activeSignIn) {
session.send("Hello! Let's get you signed in!");
builder.OAuthCard.create(connector, session, connectionName, "Please sign in", "Sign in", (createSignInErr, signInMessage) =>
{
if (signInMessage) {
session.send(signInMessage);
session.userData.activeSignIn = true;
} else {
session.send("Something went wrong trying to sign you in.");
}
});
} else {
// Some clients require a 6 digit code validation so we can check that here
session.send("Let's see if that code works...");
connector.getUserToken(session.message.address, connectionName, session.message.text, (err2, tokenResponse) => {
if (tokenResponse) {
session.send('It worked! You are now signed in with token: ' + tokenResponse.token);
session.userData.activeSignIn = false;
} else {
session.send("Hmm, that code wasn't right");
}
});
}
}
});
}
})
According to your error message, could you please double check your local botbuiler version, as the getUserToken function is added in 3.15.0, and you can find the definition at https://github.com/Microsoft/BotBuilder/blob/botbuilder%403.15.0/Node/core/src/bots/ChatConnector.ts, which doesn't show up before this version.

Auth0-js Custom Login - 401 Unauthorized

I've been working off of this React Quickstart on auth0 https://auth0.com/docs/quickstart/spa/react/02-custom-login , trying to implement a custom login. When I try to Login I get a 401 Unauthorized Error and when I try to Sign Up I get the same alert error but the user does get created and I get redirected to the home page. Mind you everything works fine using the Lock Widget but when I try to do it with the custom login it doesn't.
Here's the AuthService code which is the most relevant I feel. The Login component simply calls the login and signup methods.
export default class SocialAuthService extends EventEmitter {
constructor(clientId, domain) {
super()
// Configure Auth0
this.auth0 = new auth0.WebAuth({
clientID: 'clientID',
domain: 'domain',
responseType: 'token id_token',
redirectUri: 'http://localhost:3000/login'
})
this.login = this.login.bind(this)
this.signup = this.signup.bind(this)
this.loginWithGoogle = this.loginWithGoogle.bind(this)
this.loginWithTwitter = this.loginWithTwitter.bind(this)
this.loginWithFacebook = this.loginWithFacebook.bind(this)
}
login(username, password) {
this.auth0.client.login({
realm: 'Username-Password-Authentication',
username,
password
}, (err, authResult) => {
if (err) {
alert('Error: ' + err.description)
return
}
if (authResult && authResult.idToken && authResult.accessToken) {
this.setToken(authResult.accessToken, authResult.idToken)
browserHistory.replace('/home')
}
})
}
signup(email, password){
this.auth0.redirect.signupAndLogin({
connection: 'Username-Password-Authentication',
email,
password,
}, function(err) {
if (err) {
alert('Error: ' + err.description)
}
})
}
parseHash(hash) {
this.auth0.parseHash({ hash }, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setToken(authResult.accessToken, authResult.idToken)
browserHistory.replace('/home')
this.auth0.client.userInfo(authResult.accessToken, (error, profile) => {
if (error) {
console.log('Error loading the Profile', error)
} else {
this.setProfile(profile)
}
})
} else if (authResult && authResult.error) {
alert('Error: ' + authResult.error)
}
})
}
loggedIn() {
// Checks if there is a saved token and it's still valid
const token = this.getToken()
return !!token && !isTokenExpired(token)
}
setToken(accessToken, idToken) {
// Saves user access token and ID token into local storage
localStorage.setItem('access_token', accessToken)
localStorage.setItem('id_token', idToken)
}
setProfile(profile) {
// Saves profile data to localStorage
localStorage.setItem('profile', JSON.stringify(profile))
// Triggers profile_updated event to update the UI
this.emit('profile_updated', profile)
}
getProfile() {
// Retrieves the profile data from localStorage
const profile = localStorage.getItem('profile')
return profile ? JSON.parse(localStorage.profile) : {}
}
getToken() {
// Retrieves the user token from localStorage
return localStorage.getItem('id_token')
}
logout() {
// Clear user token and profile data from localStorage
localStorage.removeItem('id_token')
localStorage.removeItem('profile')
}
loginWithGoogle() {
this.auth0.authorize({
connection: 'google-oauth2'
})
}
loginWithTwitter() {
this.auth0.authorize({
connection: 'twitter'
})
}
loginWithFacebook() {
this.auth0.authorize({
connection: 'facebook'
})
}
}
And this is the error:
Object
code
:
"access_denied"
description
:
"Unauthorized"
original
:
Error: Unauthorized at Request.<anonymous> (http://localhost:3000/static/js/bundle.js:49311:20) at Request.Emitter.emit (http://localhost:3000/static/js/bundle.js:49954:21) at XMLHttpRequest.xhr.onreadystatechange (http://localhost:3000/static/js/bundle.js:49616:11)
statusCode
:
401
statusText
:
"Unauthorized"
Any ideas on why I'm not able to Login ?
Not sure if you got an answer, but I was running into the same problem and it was because the backend wasn't able to properly decode the JWT. Custom Login signs with a RS256 token while it seems Lock signs with HS256. You have to decode these differently in your backend.
Here's a python example
Auth0.js version 8 verifies ID tokens during authentication transactions. Only tokens which are signed with the RS256 algorithm can be verified on the client side, meaning that your Auth0 client must be configured to sign tokens with RS256. See the auth0.js migration guide for more details.
I ran into a similar issue and my fix was switching the "Application Type" setting in Auth0 from "Regular Web Application" to "Single Page Application".
The Auth0 React SDK docs make it clear that you have to register your app as a single-page application in order for Auth0 to configure the appropriate settings, so that your web client can make successful requests to their endpoints.

How to retrieve user's additional information from Azure Mobile/App Services?

I need to get the user's extra information from social accounts like Facebook and Google+. When I first read about Azure Mobile Services I thought it to be the holy grail of social authentication. Well, after a full week of hair pulling I'm starting to reconsider my first impression. It does authenticate as easily as it could possibly do. I configured Google+ and FB to work with Azure, configured Azure to use the key/secret from each provider and it all just worked. I was able to login perfectly. The problem started when I tried to get information from the logged user, which I honestly think is basic!
Azure Mobile Services returns the UserId and a Token that you can not use to request the extra info on the selected provider. So even if I were to create a second request using FB's graph API for instance, that wouldn't work (I've tried!). That token is Azure's own token. So I found out from several Carlos Figueira (SE at Azure) posts that I should customize my Azure script, make a request to Azure and then I'd be able to get it working.
I've also read several posts from Carlos Figueira on how to implement that extra functionality and even though that was not what I was looking for (customizing the server) I decided to work with that. But my return type is a MobileServiceUser and that type only has 2 properties: UserId and MobileServiceAuthenticationToken. So even after adding the server script from Carlos I couldn't retrieve the extra information from my Xamarin App.
I've read a lot of things, researched a lot and couldn't find an answer =/ By the way this is not the answer:
How to get user name, email, etc. from MobileServiceUser?
Did anyone manage to make it work?
PS: I'm not posting any code here because it's working. If you think checking some part of my code would help decipher the problem just let me know.
Thanks in advance!
EDIT:
Script
function insert(item, user, request) {
item.UserName = "<unknown>"; // default
user.getIdentities({
success: function (identities) {
var url = null;
var oauth = null;
if (identities.google) {
var googleAccessToken = identities.google.accessToken;
url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
} else if (identities.facebook) {
var fbAccessToken = identities.facebook.accessToken;
url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
} else if (identities.microsoft) {
var liveAccessToken = identities.microsoft.accessToken;
url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + liveAccessToken;
} else if (identities.twitter) {
var userId = user.userId;
var twitterId = userId.substring(userId.indexOf(':') + 1);
url = 'https://api.twitter.com/1.1/users/show.json?user_id=' + twitterId;
var consumerKey = process.env.MS_TwitterConsumerKey;
var consumerSecret = process.env.MS_TwitterConsumerSecret;
oauth = {
consumer_key: consumerKey,
consumer_secret: consumerSecret,
token: identities.twitter.accessToken,
token_secret: identities.twitter.accessTokenSecret
};
}
if (url) {
var requestCallback = function (err, resp, body) {
if (err || resp.statusCode !== 200) {
console.error('Error sending data to the provider: ', err);
request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
} else {
try {
var userData = JSON.parse(body);
item.UserName = userData.name;
request.execute();
} catch (ex) {
console.error('Error parsing response from the provider API: ', ex);
request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
}
}
}
var req = require('request');
var reqOptions = {
uri: url,
headers: { Accept: "application/json" }
};
if (oauth) {
reqOptions.oauth = oauth;
}
req(reqOptions, requestCallback);
} else {
// Insert with default user name
request.execute();
}
}
});
}
You're talking about the token on the client side correct? That token is specific only to the client. If you're using Server Side flow, the server is the only one with that token. If you want to send that to the client, you need to do that via a custom API you create.
This class you're talking about does only contain those two properties. But on your server side, your ServiceUser can access the different identity provider tokens in order to speak to those servers APIs. Your linked post is correct in how you access the token, you're mistaken on where you can access that token, it's only on the server side (if you use the server directed login flow).
Here is the custom API Script I had working in Mobile Services to return the profile of the logged in user. I am working on updating to Mobile Apps as some environment variables appear to have changed. Would love to know if anyone has gotten it to work with Mobile Apps.
exports.get = function (request, response) {
var user = request.user;
user.getIdentities({
success: function (identities) {
var req = require('request');
var url = null;
var oauth = null;
var userId = user.userId.split(':')[1];
console.log('Identities: ', identities);
if (identities.facebook) {
url = 'https://graph.facebook.com/me?access_token=' +
identities.facebook.accessToken;
} else if (identities.google) {
url = 'https://www.googleapis.com/oauth2/v3/userinfo' +
'?access_token=' + identities.google.accessToken;
} else if (identities.microsoft) {
url = 'https://apis.live.net/v5.0/me?access_token=' +
identities.microsoft.accessToken;
} else if (identities.twitter) {
var consumerKey = process.env.MS_TwitterConsumerKey;
var consumerSecret = process.env.MS_TwitterConsumerSecret;
oauth = {
consumer_key: consumerKey,
consumer_secret: consumerSecret,
token: identities.twitter.accessToken,
token_secret: identities.twitter.accessTokenSecret
};
url = 'https://api.twitter.com/1.1/users/show.json?' +
'user_id=' + userId + '&include_entities=false';
} else {
response.send(500, { error: 'No known identities' });
return;
}
if (url) {
var reqParams = { uri: url, headers: { Accept: 'application/json' } };
if (oauth) {
reqParams.oauth = oauth;
}
req.get(reqParams, function (err, resp, body) {
if (err) {
console.error('Error calling provider: ', err);
response.send(500, { error: 'Error calling provider' });
return;
}
if (resp.statusCode !== 200) {
console.error('Provider call did not return success: ', resp.statusCode);
response.send(500, { error: 'Provider call did not return success: ' + resp.statusCode });
return;
}
try {
var userData = JSON.parse(body);
response.send(200, userData);
} catch (ex) {
console.error('Error parsing response: ', ex);
response.send(500, { error: ex });
}
});
} else {
response.send(500, { error: 'Not implemented yet', env: process.env });
}
}
});
};

Resources