Refreshing token through msal.js - azure-active-directory

I'm using Azure AD B2C for my React.js app and I've noticed that after a while, all user requests are getting rejected by my API as unauthorized.
I'm pretty sure the issue is that the jwt token expires. I'm using msal.js to get my token from Azure AD B2C.
I found a short paragraph on Microsoft Docs but couldn't find any examples. Also my research indicates I need to open up a new windows and manually make an HTTP request. Is this correct?
Could someone tell me where I can find some examples of this?

Always call acquireTokenSilent before you call your API. Let MSAL do the caching, refreshing etc., that is what it is for. Fallback to AcquireTokenPopup or similar if silent fails.
https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp
function callApi() {
clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function (accessToken) {
callApiWithAccessToken(accessToken);
}, function (error) {
clientApplication.acquireTokenPopup(applicationConfig.b2cScopes).then(function (accessToken) {
callApiWithAccessToken(accessToken);
}, function (error) {
logMessage("Error acquiring the access token to call the Web api:\n" + error);
});
})
}
Side note: For acquireTokenSilent, MSAL is actually creating a hidden iframe to acquire the token, which might be what you are referring to in your question.

Related

MSAL in Angular - no_account_error: No account object provided to acquireTokenSilent

I've implemented an Azure AD login using the MSAL library on a Web App running Angular 11 and .NET Core 2.2.
The login seems to work fine, but I couldn't find any reliable info on how to handle a 401 (unauthorized) HTTP error due to an expired token.
Apparently I have to call acquireTokenSilent after having processed an interactive login, but when I do this I'm getting the following error:
Error retrieving access token: BrowserAuthError: no_account_error: No
account object provided to acquireTokenSilent and no active account
has been set. Please call setActiveAccount or provide an account on
the request.
Where can I find the setActiveAccount method? I don't see it anywhere in the MsalService Class. Also I believe the library should set the account to active after a successful login.
I'm testing this scenario by returning a 401 Error from my API after the user logged in, to trigger the acquireTokenSilent call.
Here's the code from the Interceptor that handles the 401 Error:
return next.handle(authReq).pipe(catchError((err, caught) => {
if (err instanceof HttpErrorResponse && err.status === 401) {
if(this._settings.msalAuthentication) {
console.log("Attempting to get new MSAL access token: "+this._settings.msalAuthentication.scopes);
this._msal.acquireTokenSilent({scopes: this._settings.msalAuthentication.scopes})
.subscribe(result => {
console.log("received new MSAL token: "+result);
this._dataService.handleMsalAuthenticationResult(result);
},
error => {
console.log("Error retrieving access token: "+error);
});
return EMPTY;
}
The msalAuthentication object contains the result of the initial login, including the token, user info and scopes. I don't think the user should see a popup every time the token expires.
Help would be appreciated.

React Native, unable to get access token

I'm using react-native-app-auth to get access token from azure ad b2c but I'm facing issues in android and in IOS
in android it's showing error that
Unable to complete authorization as there is no interactive call in progress. This can be due to closing the app while the authorization was in process.
https://i.stack.imgur.com/R2Fft.png
and in IOS it's redirecting but not returning anything.
that's my code
const config = {
issuer:
'https://ksg1806.b2clogin.com/tenantId/v2.0/',
clientId: 'clientId',
redirectUrl:
Platform.OS == 'ios'
? 'msauth.com.gfk.consumervoice://auth'
: 'msauth://com.gfkconsumer/Cb7s2L1nogp57%2BKdddohtF8%2Funk%3D',
additionalParameters: {},
scopes: ['openid'],
serviceConfiguration: {
authorizationEndpoint:
'https://ksg1806.b2clogin.com/ksg1806.onmicrosoft.com/b2c_1_signup_signin/oauth2/v2.0/authorize',
tokenEndpoint:
'https://gfkms.b2clogin.com/GFKMS.onmicrosoft.com/b2c_1_signup_signin/oauth2/v2.0/token',
revocationEndpoint:
'https://gfkms.b2clogin.com/GFKMS.onmicrosoft.com/b2c_1_signup_signin/oauth2/v2.0/logout',
},
};
const getAccessToken = async () => {
try {
const authState = await authorize(config);
console.log(authState);
} catch (error: any) {
Alert.alert('Failed to log in', error.message);
}
};
if anyone know about that please help I'm stuck here for many days
Please check if below points can narrow down the issue:
For v2 endpoint scope acts as resource . If the resource identifier
is omitted in the scope parameter, the resource is assumed to be
Microsoft Graph. If token is for Microsoft graph try by changing or
adding scope : User.Read . For example, scope=User.Read is
equivalent to https://graph.microsoft.com/User.Read and
offline_access is for refresh tokens Or try to Add the scope to
existing openid scope like scopes: []
For example:
scopes: ['openid', 'profile', email,'offline_access'], (or) scopes: ['openid', 'profile','offline_access']
Optional:Try also by giving client_secret inside the config
Make sure you have set the redirect url correctly.Add a trailing slash to redirect url to your config - e.g. msauth.BUNDLEID://auth/ - failure to add that may cause it to fail in IOS.
The cause may also be due to the user cancelling the operation in the middle of the process before authentication is completed.See Troubleshooting B2C | Microsoft Docs
References:
AppAuth iOS Token Exchange Problems Azure AD - Stack Overflow
Microsoft identity platform scopes, permissions, & consent | Microsoft Docs
react-native-app-auth (github.com)

How to validate AzureAD accessToken in the backend API

I just wanted to know how can we validate the azure ad access token in a backend API in my case i.e. Django rest framework.
Consider that I have a single page app or a native app and a backend API (django rest framework) completely independen of each other. In my case if my single page app/native app wants to access certain data from the backend API, and inorder to access the API, user should be logged in the backend API.
So what my approch is to make use of MSAL library to get the access token from the SPA/native app and then once token is acquired, pass that token to backend API, validate it, get the user info from graph api. If user exists in the DB then login the user and pass the required info. If user info doesn't exist then create the user, login and pass the info from the API.
So my question is when I pass the access token to my backend api, how can I validate that the token that a user/SPA/native app has passed to backend API is valid token or not?
Is it just we need to make an API call to graph API endpoint with accessToken that user/SPA/native passed and if it is able to get the user data with the accessToken then then token is valid or if it fails then the accessToken is invalid.
Is it the general way to validate the token or some better approach is there? Please help
Good day sir, I wanna share some of my ideas here and I know it's not a solution but it's too long for a comment.
I created a SPA before which used msal.js to make users sign in and generate access token to call graph api, you must know here that when you generate the access token you need to set the scope of the target api, for example, you wanna call 'graph.microsoft.com/v1.0/me', you need a token with the scope 'User.Read, User.ReadWrite' and you also need to add delegated api permission to the azure app.
So as the custom api of your own backend program. I created a springboot api which will return 'hello world' if I call 'localhost:8080/hello', if I wanna my api protected by azure ad, I need to add a filter to validate all the request if has a valid access token. So I need to find a jwt library to decode the token in request head and check if it has a token, if the token has expired and whether the token has the correct scope. So here, which scope is the correct scope? It's decided by the api you exposed in azure ad. You can set the scope named like 'AA_Custom_Impression', and then you can add this delegate api permission to the client azure ad app, then you that app to generate an access token with the scope of 'AA_Custom_Impression'. After appending the Bearer token in calling request, it will be filtered by backend code.
I don't know about python, so I can just recommend you this sample, you may try it, it's provided by microsoft.
I've solved the similar issue. I don't found how to directly validate access token, but you can just call graph API on backend with token you've got on client side with MSAL.
Node.js example:
class Microsoft {
get baseUrl() {
return 'https://graph.microsoft.com/v1.0'
}
async getUserProfile(accessToken) {
const response = await got(`${this.baseUrl}/me`, {
headers: {
'x-li-format': 'json',
Authorization: `Bearer ${accessToken}`,
},
json: true,
})
return response.body
}
// `acessToken` - passed from client
async authorize(accessToken) {
try {
const userProfile = await this.getUserProfile(accessToken)
const email = userProfile.userPrincipalName
// Note: not every MS account has email, so additional validation may be required
const user = await db.users.findOne({ email })
if (user) {
// login logic
} else {
// create user logic
}
} catch (error) {
// If request to graph API fails we know that token wrong or not enough permissions. `error` object may be additionally parsed to get relevant error message. See https://learn.microsoft.com/en-us/graph/errors
throw new Error('401 (Unauthorized)')
}
}
}
Yes we can validate the Azure AD Bearer token.
You can fellow up below link,
https://github.com/odwyersoftware/azure-ad-verify-token
https://pypi.org/project/azure-ad-verify-token/
We can use this for both Django and flask.
You can directly install using pip
but I'm not sure in Django. If Django install working failed then try to copy paste the code from GitHub
Validation steps this library makes:
1. Accepts an Azure AD B2C JWT.
Bearer token
2. Extracts `kid` from unverified headers.
kid from **Bearer token**
3. Finds `kid` within Azure JWKS.
KID from list of kid from this link `https://login.microsoftonline.com/{tenantid}/discovery/v2.0/keys`
4. Obtains RSA key from JWK.
5. Calls `jwt.decode` with necessary parameters, which inturn validates:
- Signature
- Expiration
- Audience
- Issuer
- Key
- Algorithm

Azure Web App with Acitve Directory Express with Graph API to get user photo

My Azure Web App has Active Directory enabled using the Express option. I can get the user claims/user's name from auth.me. How do I then get the user's photo/avatar? The token I get is not working in a Graph API call. I get this error from Graph API. Here is my code.
Please help! Spent hours searching and reading docs but nothing seems to address the Express AD scenario.
Thanks
Donnie
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "CompactToken parsing failed with error code: 80049217",
"innerError": {
"request-id": "e25f1fe5-4ede-4966-93c2-6d92d34da6ae",
"date": "2019-03-13T14:13:26"
}
}
}
axios.get('/.auth/me').then(resp => {
if(resp.data){
loggedInUser = {
accessToken:resp.data[0].access_token,
userId: resp.data[0].user_id,
username: resp.data[0].user_claims[9].val,
lastname: resp.data[0].user_claims[8].val,
fullname: resp.data[0].user_claims[11].val,
avatar:'https://cdn.vuetifyjs.com/images/lists/1.jpg'
}
let config = {
'headers':{
'Authorization': 'Bearer ' + loggedInUser.accessToken
}
}
axios.get('https://graph.microsoft.com/v1.0/me/photos/48x48/$value',config).then(resp => {
let photo = resp.data;
const url = window.URL || window.webkitURL;
const blobUrl = url.createObjectURL(photo);
document.getElementById('avatar').setAttribute("src", blobUrl);
loggedInUser.avatar = blobUrl;
console.log(blobUrl)
});
}
})
I was able to pull the image using MSDAL to handle the token. The new App Registration blade (as of 4/10/2019 is in preview) has a quick start which will ensure your app registration is correctly configure and allow you to download sample code.
In this blade, make sure you've added graph API permissions as shown below. When you click on Quick Start, you'll get a sample similar to this gist. It makes use of MSAL js library which handles the token negotiation.
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority,
acquireTokenRedirectCallBack, {
storeAuthStateInCookie: true,
cacheLocation: "localStorage"
});
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
After that, the magic happens in acquireTokenPopupAndCallMSGraph() which will acquire the token so you can use it to call the graph API. Now my gist makes use of XMLHttpRequest which I'm sure you'll be able to replace with axios.
To get the photo in the v1.0, it supports only a user's work or school mailboxes and not personal mailboxes.
For the details, you could read here.
Your AD app registration may not have the necessary delegate permissions. To add those permissions to your app, see these steps. I think you may need to use the oauth (login.microsoftonline.com/{{tenant}}/oauth2/v2.0/token) endpoint rather than .auth/me. With the oauth endpoint, you can even elect to pass in the scopes your token needs for calling the graph API. You can use http://jwt.ms to decode the token and see if has the necessary delegate permissions.
Also, I came across this blog series that lists various tutorials for working the Microsoft Graph. You can also check out https://github.com/microsoftgraph/nodejs-apponlytoken-rest-sample.
Furthermore, https://github.com/microsoftgraph/nodejs-connect-rest-sample makes use of passport and passport-azure-ad npm packages. That actually may be more advantagous to getting and managing tokens from Azure AD.
Hope this helps.
Ryan, I added delegate permissions to my web app's permissions settings for reading user profiles, but I still get the error message when tying to get profile pic from graph. Not sure what permissions it needs, but I basically gave it full access to use's profile. Graph just doesn't seem to like the token provided by AD Express config (login.microsoftonline.com)
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "CompactToken parsing failed with error code: 80049217",
"innerError": {
"request-id": "e25f1fe5-4ede-4966-93c2-6d92d34da6ae",
"date": "2019-03-13T14:13:26"
}
}
}
Ryan, jwt fails when I paste the full token from auth/me .

Using Firebase Auth to access the Google Calendar API

I am building a web application using AngularJS, Firebase (SDK v3) and Google Calendar API. I'm authenticating users using Google OAuth. My purpose is to be able to create calendar events from database nodes in Firebase. So far I've managed to request access to the calendar scope with:
_authProvider = new firebase.auth.GoogleAuthProvider();
// Get permission to manage Calendar
_authProvider.addScope("https://www.googleapis.com/auth/calendar");
_fbAuthObject.signInWithRedirect(_authProvider);
I'm authenticating with the redirect flow so the authentication redirect is available as:
_fbAuthObject.getRedirectResult()
.then(function _readToken(result) {
if (result.credential) {
_googleToken = result.credential.accessToken;
var authHeader = 'Bearer '+ _googleToken;
// Just a test call to the api, returns 200 OK
$http({
method: 'GET',
headers: {
'Authorization': authHeader
},
url: 'https://www.googleapis.com/calendar/v3/users/me/calendarList/primary'
})
.then(function success(response) {
console.log('Cal response', response);
},
function error(response) {
console.log('Error', response);
});
However, it seems like outside the initial login it's not possible to get the Google access token through the Firebase SDK. It seems only possible to access the Firebase JWT token, no use with the Calendar API. I could store the access token, but this wouldn't resolve the problems when refreshing the token, etc. Is there any way to get the current Google Access token with Firebase SDK and if not, what other solutions is there to the problem without having to authenticate the user twice?
UPDATE 1:
Seems like someone else has struggled with similar problems with Facebook authentication. On that question there was a link to the Firebase documentation stating that Firebase Authentication no longer persists the access token. So how can I handle token refreshes? Is there really no answer to this?
UPDATE 2:
So, I contacted Firebase Support with a feature request about this problem and they gave me the following answer:
Thanks for taking your time to write us.
I've got your point here, this is indeed a good suggestion. We're definitely aware that many users, such as yourself, would like OAuth feature that will access token upon refresh. We're exploring potential solutions, but I cannot guarantee if this will be available anytime soon. We'll keep your feedback in consideration moving forward though. Continuous improvement is very important for our community, so thanks for bringing this up!
Keep an eye out on our release notes for any further updates.
So It seems like the access tokens are not available through the Firebase SDK at the moment. I'm still trying to find a workaround, so if anyone has ideas about a valid solution I'd be glad to hear them. And of course, I'll be posting it here if I ever find a working solution myself.
I finally got around this problem by handling the authentication outside Firebase with the Google APIs JavaScript client. This solution requires including the Google auth client as documented here. Manually handling the Firebase sign-in flow is documented here.
gapi.auth2.getAuthInstance().signIn()
.then(function _firebaseSignIn(googleUser) {
var unsubscribe = firebase.auth().onAuthStateChanged(function(firebaseUser) {
unsubscribe();
// Check if we are already signed-in Firebase with the correct user.
if (!_isUserEqual(googleUser, firebaseUser)) {
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(
googleUser.getAuthResponse().id_token);
// Sign in with credential from the Google user.
return firebase.auth().signInWithCredential(credential)
.then(function(result) {
// other stuff...
});
The _isUserEqual function:
function _isUserEqual(googleUser, firebaseUser) {
if (firebaseUser) {
var providerData = firebaseUser.providerData;
for (var i = 0; i < providerData.length; i++) {
if (providerData[i].providerId === firebase.auth.GoogleAuthProvider.PROVIDER_ID &&
providerData[i].uid === googleUser.getBasicProfile().getId()) {
// We don't need to reauth the Firebase connection.
return true;
}
}
}
return false;
}
Now I can reference the access token like this:
var user = gapi.auth2.getAuthInstance().currentUser.get();
return user.getAuthResponse().access_token;
This still isn't the ideal solution for me, but it works for now, and I'm able to authenticate to both Firebase and the Calendar API.

Resources