Using authProvider with MS SDK for graph calls in C# - c#

I'm trying create a C# console application to connect to graph API and get a list of users from AzureAD from a tenant. I have registered the app and the admin has given me the following
Tenant Name and Tenant ID
Client ID (also sometimes called App Id)
Client Secret
Using the sdk the C# code I need to use looks like this (https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=cs):
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var users = await graphClient.Users
.Request()
.GetAsync();
However, the console application will run as a batch process so there will be no user interaction at all. So in order to provide the authProvider I followed this article on MS docs site: https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS
And I think for my purpose I need to go for the "Client Credential OAuth flow". The code which is shown on that URL. But here it is too.
IConfidentialClientApplication clientApplication = ClientCredentialProvider.CreateClientApplication(clientId, clientCredential);
ClientCredentialProvider authProvider = new ClientCredentialProvider(clientApplication);
The trouble is that Visual Studio does not recognise ClientCredentialProvider class. I'm not sure which assembly to import. I'm using the following usings in the top.
using Microsoft.Identity.Client;
using Microsoft.IdentityModel.Clients;
using Microsoft.IdentityModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
I'm not very experienced with GitHub repos and I'm using Visual Studio 2015. I would be interested in sample code; I have looked but cannot find any. MS have some lectures but they use another type of auth Provider which is authenticating interactively which is not what I'm looking for. I want obtain the token using the TenantId/ClientId and Client Secret.

ClientCredentialProvider is part of the Microsoft.Graph.Auth package. You can read more about this package at https://github.com/microsoftgraph/msgraph-sdk-dotnet-auth
Note that this package is currently (as of 2019-05-15) in preview, so you may want to wait before using this in a production application.
Alternatively, the following example uses the Microsoft Authentication Library for .NET (MSAL) directly to set up the Microsoft Graph SDK using app-only authentication:
// The Azure AD tenant ID or a verified domain (e.g. contoso.onmicrosoft.com)
var tenantId = "{tenant-id-or-domain-name}";
// The client ID of the app registered in Azure AD
var clientId = "{client-id}";
// *Never* include client secrets in source code!
var clientSecret = await GetClientSecretFromKeyVault(); // Or some other secure place.
// The app registration should be configured to require access to permissions
// sufficient for the Microsoft Graph API calls the app will be making, and
// those permissions should be granted by a tenant administrator.
var scopes = new string[] { "https://graph.microsoft.com/.default" };
// Configure the MSAL client as a confidential client
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/$tenantId/v2.0")
.WithClientSecret(clientSecret)
.Build();
// Build the Microsoft Graph client. As the authentication provider, set an async lambda
// which uses the MSAL client to obtain an app-only access token to Microsoft Graph,
// and inserts this access token in the Authorization header of each API request.
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => {
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
// Make a Microsoft Graph API query
var users = await graphServiceClient.Users.Request().GetAsync();
(Note that this example uses the latest version of the Microsoft.Identity.Client package. Earlier versions (before version 3) did not include ConfidentialClientApplicationBuilder.)

As mentioned above - at the time of writinf Sept 2021, the package: Microsoft.Graph.Auth is deprecated. This means ClientCredentialProvider is no longer available.
https://www.nuget.org/packages/Microsoft.Graph.Auth/
The alternative is to use the solution propsed by Philippe above.

If you wanna cycle trough the users, replace the var users with the following code:
IGraphServiceUsersCollectionPage users = graphServiceClient.Users.Request().GetAsync().Result;
foreach (User user in users)
{
Console.WriteLine("Found user: " + user.Id);
}

Related

How to authenticate correctly in SharePoint Online from Azure Function with API Graph?

Ciao,
I'm working on a Azure Function that need to read/write to a SharePoint Online list using API Graph. I've some problems on authentication.
I've followed this steps:
Created one SharePoint Online site and one list
Registered my app in Azure Active Directory (single-tenant)
Generated one secret
Added Sites.Selected authorization to my app
Requested permissions to my administrator following this link
Wrote code for use API Graph
Below app's authorizations:
Below my code:
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "my-tenant-id";
var clientId = "my-client-id";
var clientSecret = "my-secret";
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes);
var test = await graphServiceClient
.Sites["my-site-id"]
.Lists["my-list-id"]
.Items.Request().GetAsync();
When I execute this code I obtain this error: Message: Either scp or roles claim need to be present in the token.
How can I resolve this error?
Thank you a lot
I tried to reproduce the same in my environment and got the same error as below:
To resolve the error, I created an Azure AD Application and granted API Permissions like below:
Note that, you can only add Sites.Selected API permission based on your requirement.
I generated the token with scope as https://graph.microsoft.com/.default by using below parameters:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:https://graph.microsoft.com/.default
grant_type:client_credentials
Using the above generated access token, I am able to authenticate to SharePoint successfully like below:
https://graph.microsoft.com/v1.0/sites/

How to generate an on-behalf-of token for a middle-tier API

I'm trying to implement an on-behalf-of flow, with Microsoft Identity Platform where my Web app authenticates users, then makes a request to my Web API which in turn makes a request to the Microsoft Graph API (and returns the result to the Web app).
My problem is that I will need to pass on an on-behalf-of token to my Web API for it to be granted acces to Microsoft Graph, but I cannot manage to generate this token. (I'm trying to generate this token using Postman at the moment.)
What I want to be able to run is a code snippet provided by the official documentation here for the Microsoft Graph SDK (in the case of an OBO-flow), and what I need help with is how to generate the token for the oboToken variable.
using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Identity.Client;
var scopes = new[] { "User.Read", "Presence.Read.All" };
var tenantId = "common";
var clientId = "<id of my API as registered in Azure AD / App Registrations>";
var clientSecret = "<value from Client Secret in Registerd Application / Certificates & secrets";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var oboToken = "< WHAT NEEDS TO BE PROVIDED BY THE WEB APP >";
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new DelegateAuthenticationProvider(async (request) => {
var assertion = new UserAssertion(oboToken);
var result = await cca.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
var graphClient = new GraphServiceClient(authProvider);
Console.WriteLine(graphClient.Me.Request().GetAsync().Result);
I've tried the following: I generate an authorization code with the following request in my browser
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=< Id of my API >
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp
&response_mode=query
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
Then, with the returned code, make the following POST request (from Postman)
https://login.microsoftonline.com/common/oauth2/v2.0/token?
client_id=< Id of my API >
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp
&grant_type=authorization_code
&code=< code returned by request above >
When I use the returned token from the response as the value of the oboToken in the code snippet, I get a
MsalServiceException: AADSTS50027: JWT token is invalid or malformed.
If I instead also include a client_secret parameter in the POST request above for a token, I get the response AADSTS90023: Public clients can't send a client secret.
How could I generate an on-behalf-of token to be able to run the provided code snippet?
Thanks in advance!
It seems to me that your mistake is getting an MS Graph API token with the authorization code flow.
The way it should work is:
Web App gets an access token to the Web API using authorization code flow
Web API receives the access token and exchanges it for an MS Graph API token using the on-behalf-of flow
Web API calls MS Graph API
So when your Web App gets an access token, it should use a scope defined in the API app registration instead of scope=https%3A%2F%2Fgraph.microsoft.com%2F.default.
If you specify an MS Graph API scope, you get an access token for MS Graph API, meaning you are trying to call MS Graph API from the Web App, instead of your API.

Graph Api Permission to Read Email

I have the following c# code. I am trying to read email in exchange 365 using graph api as per the following link
Reading user emails using MS Graph API C#
IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder
.Create("myAppId")
.WithTenantId("myTenantId")
.WithClientSecret("myClientSecret")
.Build();
ClientCredentialProvider ccp = new ClientCredentialProvider(cca);
GraphServiceClient client = new GraphServiceClient(ccp);
var users = await client.Users.Request()
.GetAsync();
I executed it and got the following error:
Microsoft.Graph.ServiceException: 'Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation.
I have given the mail.read and user.read permission as per screenshot.
any advices?
Thanks.
As your code shows, you use the client credential flow which uses application permissions.
If you want to use var users = await client.Users.Request().GetAsync(); to list users, one of the application permissions need to be set.

Azure Active directory integration c# windows application

We need to integrate the Azure Active directory with the our Add-in project for Login implementation single sign on mode.
So, in this case how can we get logged in user’s email address?
As shown in below screenshot, we are unable to fetch user email address if it is on Azure Active Directory.
If Active directory is on local server, we are able to fetch the email address. Using following code snippet
( System.DirectoryServices.AccountManagement.UserPrincipal.Current.EmailAddress )
we have already tried using graph API. Although /me not working. we are getting exception as Request_ResourceNotFound
If you want to call Graph API with the SDK in C# application, please refer to the following steps:
Install SDK
Install-Package Microsoft.Graph
Install-Package Microsoft.Graph.Auth -IncludePrerelease
Code
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
For more details, please refer to the document and the article.
Update
If we use client credential authentication, please use the following code to get one user
var user = await graphClient.User
.Request()
.Filter("mail eq \'<your emial>\'")
.GetAsync();

How to retrieve an MS Graph access token for a .NET Core Web API without user sign in

UPDATE (solution): I ended up simply extracting the token from the request that my frontend is sending with:
private async Task<string> GetApplicationAccessToken()
{
var token = this.Request
.Headers["Authorization"]
.First()
.Substring("Bearer ".Length);
var assertion = new UserAssertion(token, _ASSERTION_TYPE);
var authResult= await this._app.AcquireTokenOnBehalfOf(new []{""}, assertion)
.ExecuteAsync();
return authResult.AccessToken;
}
ORIGINAL:
I want to funnel data from the MS Graph API (Azure AD endpoint) through my backend (.NET Core Web API) back to my Angular App, that requests the data.
I am running into the problem that I am unable to get an Access token in my backend Web API.
I have Implemented a graph service according to this sample where user consent is prompted through a static html page that is being hosted on the web API. But I want to access MS Graph without explicit user consent.
I have looked for ways to get an access token for my web API without user consent, but not found anything helpful. Only stuff that confuses me. I have also supplied the App registration in Azure AD with application permissions and supplied my web API with sufficient information to the Azure app.
I am still not sure how to exactly adapt the sample code to work with my scenario where user consent is not required / an token already present in the request that my Angular app makes to my web API.
I am getting a userId (objectId.tenantId) in my GraphAuthProvider class when I am trying to call GetAccountAsync(). Yet I still don't receive a token from that call and don't get any error hints, just null.
public async Task<string> GetUserAccessTokenAsync(string userId)
{
var account = await _app.GetAccountAsync(userId);
if (account == null)
{
throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
}
My appsettings.json
"AzureAd": {
"CallbackPath": "/signin-oidc",
"BaseUrl": "https://localhost:63208",
"ClientId": "[redacted]",
"TenantId": "[redacted]",
"ClientSecret": "[redacted]", // This sample uses a password (secret) to authenticate. Production apps should use a certificate.
"Scopes": "user.read profile",
"GraphResourceId": "https://graph.microsoft.com/",
"GraphScopes": "User.Read.All Groups.Read.All"
}
Can you point me in the right direction as to how to call the MS Graph API from my backend by using the application permissions?
Client credential flow using directly http post
In you web api , you can directly create http request to authenticate using client credential flow and retire Microsoft Graph's access token :
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_secret=qWgdYAmab0YSkuL1qKv5bPX
&grant_type=client_credentials
Before that , you'd better admin consent the app permissions , see the detail steps in this article .
Client credential flow using MSAL.NET
If using the MSAL.NET , you can use below code sample for client credential flow :
// Even if this is a console application here, a daemon application is a confidential client application
IConfidentialClientApplication app;
#if !VariationWithCertificateCredentials
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId("{tenantID}")
.WithClientSecret(config.ClientSecret)
.Build();
#else
// Building the client credentials from a certificate
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId("{tenantID}")
.WithCertificate(certificate)
.Build();
#endif
// With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by
// a tenant administrator
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
}
catch(MsalServiceException ex)
{
// Case when ex.Message contains:
// AADSTS70011 Invalid scope. The scope has to be of the form "https://resourceUrl/.default"
// Mitigation: change the scope to be as expected
}
You can refer to this article and code sample on Github.
Client credential flow using Microsoft Graph .NET authentication library
From document : https://github.com/microsoftgraph/msgraph-sdk-dotnet-auth
You can use Client credential provider :
// Create a client application.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
// Create an authentication provider.
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
// Configure GraphServiceClient with provider.
GraphServiceClient graphServiceClient = new GraphServiceClient(authenticationProvider);
Or directly use MSAL.NET to authenticate using client credential flow and build the Microsoft Graph client like reply from #Philippe Signoret shows .

Categories

Resources