Issues Accessing Microsoft Graph API Data - c#

Currently having issues integrating Microsoft Graph API, into my ASP.NET Core 2.2 Web Application(MVC). That uses "Work or Schools Accounts" : “Cloud – Single Organisation” using Two Factor Azure Sign-on Authentication.
Using Code Sample 1 code I'm attempting to GET the graph query: -
https://graph.microsoft.com/v1.0/me/
returning the surname from the response header
The issue that i'm experiencing at the moment is that i'm receiving an error at the line of code: -
var objMessages = objGraphClient.Me.Request().GetAsync().Result;
With the error message : "does not exist or one of its queried reference-property objects are not present".
// #############
// Code Sample 1
// #############
// Graph Api.
string strResource = "https://graph.microsoft.com";
string SecretId = "<Secret Id>";
// Azure Ad.
Uri strInstance = new Uri("https://login.microsoftonline.com/");
string strDomain = "<Domain>.onmicrosoft.com";
string strTenantId = "<Tenant Id>";
string strClientId = "<Client Id>";
string strCallbackPath = "/signin-oidc";
// The authority to ask for a token: your azure active directory.
string strAuthority = new Uri(strInstance, strTenantId).AbsoluteUri;
AuthenticationContext objAuthenticationContext = new AuthenticationContext(strAuthority);
ClientCredential objClientCredential = new ClientCredential(strClientId, SecretId);
// Acquire Token.
AuthenticationResult objAuthenticationResult = objAuthenticationContext.AcquireTokenAsync(strResource, objClientCredential).Result;
// Get bearer token.
GraphServiceClient objGraphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async request =>
{
// This is adding a bearer token to the httpclient used in the requests.
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", objAuthenticationResult.AccessToken);
}));
// The next line produces an error :: does not exist or one of its queried reference-property objects are not present.
var objResult = objGraphClient.Me.Request().GetAsync().Result;
Debug.WriteLine($"{objResult.Surname}");
If I change Code Sample 1 above to Code Sample 2 below passing in the tokenPlease() requested that’s obtained from Microsoft Graph Explorer after successful login, this works, returning the surname successfully, indicating that their is an issue possible in my Bearer token: -
// #############
// Code Sample 2
// #############
// Get bearer token.
GraphServiceClient objGraphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async request =>
{
// This is adding a bearer token to the httpclient used in the requests.
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer","ERf54f2f...Etc");
}));
// The next line now works.
var objResult = objGraphClient.Me.Request().GetAsync().Result;
Debug.WriteLine($"{objResult.Surname}");
Any help on this would be much appreciated!

You are using the ADAL library which uses the old Azure AD V1 authentication endpoint. You should be using the MSAL Library which uses the Azure AD V2 authentication endpoint.
I would suggest making your life easy and go grab the Microsoft.Graph.Auth Nuget package and then use this code instead of having to create your own
DelegateAuthenticationProvider
IConfidentialClientApplication clientApplication = AuthorizationCodeProvider.CreateClientApplication(clientId, redirectUri, clientCredential);
AuthorizationCodeProvider authenticationProvider = new AuthorizationCodeProvider(clientApplication, scopes);

Related

MSAL System.InvalidOperationException: CompactToken parsing failed with error code: 80049217

I'm using MSAL for .NET to acquire tokens for my Graph API requests, but out of sudden, I'm getting following error, which I can see a lot of post about, but no solution of reason why Error 80049217 happens? Does anyone know why this error occurs and maybe a solution to avoid the error?
System.InvalidOperationException: CompactToken parsing failed with
error code: 80049217
UPDATE 22-01-10
Example of method to acquire access token (Client is instance of HttpClient reused by all threads using the factory class containing this method. _confidentialClient is an instance of IConfidentialClientApplication in the MSAL .NET library):
private IConfidentialClientApplication _confidentialClient;
public void Initialize()
{
// Construct the ConfidentialClientApplication
_confidentialClient =
ConfidentialClientApplicationBuilder.Create("clientId")
.WithClientSecret("clientSecret")
.WithAuthority("authority")
.Build();
}
// Multiple threads will access this method
private async Task GetAccessToken()
{
try
{
Console.WriteLine("Acquire token....");
// Is the .AcquireTokenForClient method thread safe??
var result = await _confidentialClient.AcquireTokenForClient("https://graph.microsoft.com/.default").ExecuteAsync();
if(Client.DefaultRequestHeaders.Authorization?.Parameter == result.AccessToken)
{
Console.WriteLine("Token havn't changed.");
return;
}
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
Console.WriteLine("Acquire token successfully!");
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
All threads interacting with Graph API by this factory class will start by calling the GetAccessToken method to make sure the HttpClient has a valid AccessToken in the Authorization header. As far as I have read about IConfidentialClientApplication, the AcquireTokenForClient() will look for valid tokens in the internal cache, and if there isn't any, acquiring a new one, which is why the method is always invoked by any thread.
UPDATE 22-01-13:
Added some logic of how the IConfidentialClientApplication is built.
It seemed that you wrote this code in your asp.net core backend project, and you wanna a method to help generate access token for different scopes without entering user name/password to sign in, so that it can serve different scenario. But you made a mistake here.
See this document first. In a server/daemon application, you can only use client credential flow to generate access token, so the scope for graph api should be https://graph.microsoft.com/.default, and this section provides the sample code to use client credential flow in your asp.net core app. Here's the snippet.
var scopes = new[] { "https://graph.microsoft.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "common";
// Values from app registration
var clientId = "YOUR_CLIENT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);

Azure AD B2C with WebApi2- calling GraphAPI after authentication

I have a WebApi2 app which servers as api for my app frontend. Now i want to use AD B2C to manage my users - let's say I want to differentiate them by their roles (admin or customer) and for that i created two b2c users groups accordingly. When user logs in i want to display different things for users with different roles (groups).
I'm using this example to setup Startup.Auth.cs in my WebApi2 project:
var tvps = new TokenValidationParameters
{
ValidAudience = clientId,
AuthenticationType = signUpSignInPolicy,
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(aadInstance, tenant, defaultPolicy))),
});
From what I have read b2c doesn't return user's grups in claims for now. Some people suggested I need to call GraphApi after obtaining token to fetch these groups and add them to user's claims:
private static async Task<string> GetGroups(string token, string userId)
{
using (var client = new HttpClient())
{
var requestUrl = $"https://graph.microsoft.com/v1.0/users/{userId}/memberOf?$select=displayName";
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
}
This is where I've stuck. How can I inject my code to get token for calling graph? I've messed with OAuthBearerAuthenticationOptions.Provider:
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = (context) =>
{
// var token = ??
// var userId = <get from context's claims>
// var groups = GetGroups(token, userId);
// <add to claims>
return Task.CompletedTask;
}
},
...but I don't know how to get to token. Maybe that's wrong from the start and I need another approach?
Customer's token cannot be used to call AADGraph/MSGraph Apis. To get token to call graph apis in an automated way, we need app-only access. We need to configre an app in the tenant, the crendetial of which are used to get a token. That token can then be used to call memberOF Api (or any other api which does or require user information to be there)
Here is the sample and explaination of how to call AAD Graph apis in a B2C dependent service.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet

How to properly obtain the token using C# from Identity Server 4 to use in Postman?

I'm executing the following C# magic and read the token obtained in jwt.io. All's looking great.
DiscoveryResponse vasco = DiscoveryClient.GetAsync("http://localhost:5100").Result;
string tokenUri = vasco.TokenEndpoint;
TokenClient client = new TokenClient(vasco.TokenEndpoint, "Blopp", "SuperSecret");
TokenResponse cred = client.RequestClientCredentialsAsync("secured_api").Result;
string token = cred.AccessToken ?? "none!";
However, it seems not to be entirely well functioning one, because when pasted into Postman using key Authorization and value Bearer + token (the prefix daded manually), I get into the service not being reachable (as discussed in this question).
Using the same credentials on the endpoint http://localhost:5100/connect/token and Postman's OAuth 2.0 based wizard, produces a token that works.
My conclusion's that I somehow don't fetch the proper token using my code (and fail to realize it due to ignorance) or that I fetch a token that's missing something.
How do I fetch the proper token, complete and entirely equivalent to the one that Postman obtains at the URL above?
My conclusion's that I somehow don't fetch the proper token using my code (and fail to realize it due to ignorance) or that I fetch a token that's missing something.
From your codes , you are protecting an API using Client Credentials, so firstly please follow the detailed steps in article to config the identity server , web api and the clients .
For testing , i follow the steps in the article , and use same codes as you shown to acquire token :
// discover endpoints from metadata
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// request token
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
Console.WriteLine("\n\n");
'http://localhost:5000' is the identity server's host endpoint and clinet/secret is the credential of my client :
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
}
Use that token to access the web api in Postman :
You can also compare the acquiring token request when using the OAuth 2.0 based wizard and confirm that you are using the client credential flow .

How do I use OpenID and Microsoft Graph to enumerate Azure ActiveDirectory groups?

I have the following values:
OpenID App Key
OpenID Audience
OpenID Client ID
OpenID Login URL/Domain
Token Endpoint (https://login.windows.net/<tenant-id>/oauth2/token)
Resource URL (https://graph.windows.net)
How do I use these values to create a Microsoft Graph service client?
var graphClient = new GraphServiceClient(
// What goes here?
);
I need the client to enumerate AAD groups.
Based on your description, I assumed that you are using the AAD v1.0, for using the Microsoft Graph client SDK, you need to add Required permissions to the Microsoft Graph API with the application permissions or delegated permissions for your AAD application on Azure Portal. Differences between application permissions and delegated permissions, you could follow here.
For web application and use the user-based authentication flow, you could follow the samples below:
Calling the Azure AD Graph API in a web application
Microsoft Graph Snippets Sample for ASP.NET 4.6
Note: For your scenario, you need to combine the code in the above two samples. Or you could just create the AAD v2.0 application and just use the second sample.
For server to server scenario, you could just use ADAL to retrieve the access token to initialize your GraphServiceClient:
private static async Task<string> GetAccessTokenAsync()
{
string tenantId = "<tenantId>";
string clientId = "<clientId>";
string clientSecrets = "<clientSecrets>";
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult result = null;
var context = new AuthenticationContext(String.Format("https://login.windows.net/{0}", tenantId));
var authParam = new PlatformParameters(PromptBehavior.Never, null);
var result = await context.AcquireTokenAsync(
"https://graph.microsoft.com"
, new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(clientId, clientSecrets)
);
return result.AccessToken;
}
//initialize the GraphServiceClient instance
var graphClient = new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
var token = await GetAccessTokenAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}));

Server-side task to query Office 365 account for new emails

I need a server-side task on my .NET 4.6.1/MVC 5 app that will periodically check a specific O365 email address for new emails and retrieve them if found. This seems like a stupidly simple task, but I cannot find documentation anywhere for creating a server-side process to accomplish this. The only documentation Microsoft seems to have is for OAuth2 and passing through credentials when users sign in. I don't want that. I want to check one specific account, that's it. How would I accomplish this?
These are the pages I've found. There are others, but all are along these lines.
Intro to the Outlook API - I don't see a way to use a service account with the v2 endpoint.
Get Started with the Outlook REST APIs - This is specific to logging users in with OAuth2, unhelpful for my purposes.
Intro to the Outlook API - I don't see a way to use a service account with the v2 endpoint.
The v2 endpoint doesn’t support client credential at present( refer to the limitation). You need to register/configure the app using Azure portal and use the original endpoint to authenticate the app. More detail about register the app please refer to here. And we need to ‘read mail in all mailbox’ to use the client credential to read the messages like figure below.
And here is the code that using client credential to read messages using the Microsoft Graph:
string clientId = "";
string clientsecret = "";
string tenant = "";
string resourceURL = "https://graph.microsoft.com";
string authority = "https://login.microsoftonline.com/" + tenant + "/oauth2/token";
string userMail = "";
var accessToken = new TokenHelper(authority).AcquireTokenAsync(clientId, clientsecret, resourceURL);
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.FromResult(0);
}));
var items = await graphserviceClient.Users[user].Messages.Request().OrderBy("receivedDateTime desc").GetAsync();
foreach (var item in items)
{
Console.WriteLine(item.Subject);
}
class TokenHelper
{
AuthenticationContext authContext;
public TokenHelper(string authUri)
{
authContext = new AuthenticationContext(authUri);
}
public string AcquireTokenAsync(string clientId, string secret,string resrouceURL)
{
var credential = new ClientCredential(clientId: clientId, clientSecret: secret);
var result = authContext.AcquireTokenAsync(resrouceURL, credential).Result;
return result.AccessToken;
}
}
In addition, if we authenticate the app with code grant flow we can also create a subscription which notify the app when the mail box receive the new messages.( refer to webhoocks/subscription)

Categories