microsoft-graph-client throws '#<GraphError>' error on API call - azure-active-directory

I would like to consume the Graph API from my Node backend. First I get an access token from adal-node by calling the acquireTokenWithClientCredentials method. So my token response would be
I install the #microsoft/microsoft-graph-client package and tried to follow the guide from here https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/samples/node/main.js . For the isomorphic-fetch import I installed the isomorphic-fetch package.
async function bootstrap() {
// ...
const session: TokenResponse = await authenticationService.getSession(); // call the 'acquireTokenWithClientCredentials' function
const client: Client = Client.init({
authProvider: (done) => {
done(null, session.accessToken);
},
});
try {
await client.api('/me').get();
} catch (error) {
throw error;
}
}
The API call throws an exception. But I can't figure out what's wrong because the error only logs
(node:20561) UnhandledPromiseRejectionWarning: #<GraphError>
What is wrong or missing? Please let me know if you need more information. But I hope that my description should be enough for reproduction :) Thanks in advance.
Update
The application does not sign in as a user and should run as a background service. So maybe I can't access /me because the application is not a user. But when I update /me to /users (this endpoint should work) then I still get the same error.
So my current access token when accessing /users is

You can not use Client credential flow to get access token for /me endpoint. You should use auth code flow(acquireTokenWithAuthorizationCode) instead. Then you can call /me for the signed-in user.
If you must use client credential flow, you should call /users/{id | userPrincipalName} for a specific user.
Besides, if you want to call Microsoft Graph API, the value of resource which you used to get access token should be https://graph.microsoft.com.
var resource = 'https://graph.microsoft.com';

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.

Azure Authentication from client app to another API

I'm trying to get azure AD authentication working between a Blazor WASM app, and another API that I have running locally but on a different port. I need both applications to use the Azure login, but I only want the user to have to log in once on the Blazor app which should then pass those credentials through to the API.
I've set up app registrations for both apps in the portal, created the redirect url, exposed the API with a scope and I can successfully log into the blazor app and see my name using #context.User.Identity.Name.
When it then tries to call the API though, I get a 401 error back and it doesn't hit any breakpoints in the API (presumably because there is no authentication being passed across in the http request).
My code in the Blazor app sets up a http client with the base address set to the API:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("APIClient", client => client.BaseAddress = new Uri("https://localhost:11001"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("APIClient"));
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://d3152e51-9f5e-4ff7-85f2-8df5df5e2b2e/MyAPI");
//options.UserOptions.RoleClaim = "appRole";
});
await builder.Build().RunAsync();
}
In my API, I just have the Authorise attribute set on the class, and eventually will need roles in there too:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class CarController
Then, in my Blazor component, I then inject the http factory and try to make a request:
#inject IHttpClientFactory _factory
...
private async Task RetrieveCars()
{
var httpClient = _factory.CreateClient("APIClient");
HttpResponseMessage response = await httpClient.GetAsync("https://localhost:11001/api/cars");
var resp = await response.Content.ReadAsStringAsync();
cars = JsonSerializer.Deserialize<List<Car>>(resp);
}
but this returns the 401 error. I've also tried a few different variations like just injecting a http client (#inject HttpClient Http) but nothing seems to be adding my authorisation into the API calls. The options.UserOptions.RoleClaim is also commented out in the AddMsalAuthentication section as I wasn't sure if it was needed, but it doesn't work with or without it in there.
Can anyone explain what I'm doing wrong and what code I should be using?
Common causes.
Most cases ,we tend to forget to grant consent after giving API
permissions in the app registration portal,after exposing the api
which may lead to unauthorized error.
Other thing is when Audience doesn’t match the “aud” claim when we
track the token in jwt.io .Make sure ,Audience=clientId is configured
in the code in authentication scheme or Token validation parameters
by giving ValidAudiences.And also try with and without api:// prefix
in client id parameter.
Sometimes aud claim doesn’t match as we mistakenly send ID token
instead of Access tokens as access tokens are meant to call APIs .So
make sure you check mark both ID Token and access token in portal
while app registration.
While Enabling the authentication by injecting the [Authorize]
attribute to the Razor pages.Also add reference
Microsoft.AspNetCore.Authorization as(#using
Microsoft.AspNetCore.Authorization)
Please see the note in MS docs and some common-errors
If above are not the cases, please provide with additional error details and startup configurations or any link that you are following to investigate further.

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 .

Refreshing token through msal.js

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.

Resources