Execute the Azure DevOps REST API using an access token - c#

I have created an ASP.NET Framework application using Microsoft Identify Platform from the standard template and used ConfidentialClient to acquire an access token. I now want to use this access token to call the Azure DevOps REST API.
My scenario is:
Open the application and immediately get asked to log in
Acquire an access token from ConfidentialClient
Execute an API call to Azure DevOps (e.g. GET https://dev.azure.com/{organization}/_apis/projects)
I believe I have completed steps 1 and 2 (code below), but when I execute the API is doesn't return the results, merely a HTML page asking me to login
The access token is recovered from the following code:
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var authCode = context.Code;
var tenantId = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
var authority = aadInstance + tenantId;
//string[] scopes = new string[] { "https://graph.microsoft.com/User.Read" };
string[] scopes = new string[] { "https://app.vssps.visualstudio.com/user_impersonation" };
//string[] scopes = new string[] { "https://graph.microsoft.com/User.Read", "https://app.vssps.visualstudio.com/user_impersonation" };
// Get the access token from the ConfidentialClientApplication)
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithRedirectUri(redirectUri)
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.Build();
var authResult = await app.AcquireTokenByAuthorizationCode(scopes, authCode).ExecuteAsync();
string accessToken = authResult.AccessToken;
Debug.WriteLine($"Access Token: {accessToken}");
//await GetProfileData(accessToken);
await GetProjectList(accessToken);
}
If I run this I get the access token but using this as the bearer token in my API call doesn't work. The method for calling the API is as follows:
private async Task GetProjectList(string accessToken)
{
// Get the Project List from the Azure DevOps API
var httpClient = new HttpClient();
var httpRequest = new HttpRequestMessage(HttpMethod.Get,
"https://dev.azure.com/gp-ementris/_apis/projects");
httpRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", accessToken);
var response = await httpClient.SendAsync(httpRequest);
if (response.IsSuccessStatusCode)
{
Debug.WriteLine(await response.Content.ReadAsStringAsync());
}
}
Can someone help explain how I can get the API to work with the token?
Thanks

Related

How can I authorize REST API for an application rather than for a user?

I have a mobile app, which needs to call a REST API. Here is my code:
string url = $#"https://graph.microsoft.com/v1.0/solutions/bookingBusinesses/{adTenantId}/appointments";
string accessToken = new JwtSecurityTokenHandler().WriteToken(AuthService.JwtToken);
HttpClient client = new();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.GetAsync(url).ConfigureAwait(false);
This gives me error 401 (Unauthorized). I may be wrong, but it seems to me that it is because I use the access token based on the user's authentication. I probably need to call the API not as the user, but as my app. Buy I don't know how to get an access token for the app.
The app is registered with Azure AD and has necessary API permissions.
Assume you have set up the app for MS Graph API correctly, and you have the below configurations:
ClientId
ClientSecret
TenantId
Scopes
var app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithClientSecret(ClientSecret)
.WithAuthority(new Uri("https://login.microsoftonline.com/" + Tenant))
.Build();
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
var accessToken = result.AccessToken;
Note that I use scope https://graph.microsoft.com/.default for all the permissions you have assigned to the application. You could use more specific scopes.
References.

Microsoft.Graph C#: Make an API request programmatically

I'm working with Microsoft.Graph SDK and I need to get email SentItems programmatically in a class library.
I'm using the following code to create a client:
private static Graph.GraphServiceClient CreateClient()
{
var scopes = new[] { "User.Read" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "xxx";
// Value from app registration
var clientId = "xxxx";
var pca = Microsoft.Identity.Client.PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.Build();
// DelegateAuthenticationProvider is a simple auth provider implementation
// that allows you to define an async function to retrieve a token
// Alternatively, you can create a class that implements IAuthenticationProvider
// for more complex scenarios
var authProvider = new Graph.DelegateAuthenticationProvider(async (request) =>
{
// Use Microsoft.Identity.Client to retrieve token
var result = await pca.AcquireTokenByIntegratedWindowsAuth(scopes).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
return new Graph.GraphServiceClient(authProvider);
}
then I'm trying to use the client the next waay:
var sentEmails = graphClient.Users[authMail].MailFolders.SentItems.Request().GetAsync().Result;
but I'm getting the following exception when executing the request:
Exception thrown: 'Microsoft.Identity.Client.MsalUiRequiredException'
in System.Private.CoreLib.dll Exception thrown:
'System.AggregateException' in System.Private.CoreLib.dll
I thought that another option could be to get an auth token. I can get an auth token with the next code:
private static async Task<string> GetGraphToken()
{
var resource = "https://graph.microsoft.com/";
var instance = "https://login.microsoftonline.com/";
var tenant = "xxx";
var clientID = "xxxx";
var secret = "xxxxx";
var authority = $"{instance}{tenant}";
var authContext = new AuthenticationContext(authority);
var credentials = new ClientCredential(clientID, secret);
var authResult = authContext.AcquireTokenAsync(resource, credentials).Result;
return authResult.AccessToken;
}
And it just works, but then I don't know how to use it to do an API request programmatically.
Any of the two variants is OK for me, getting rid of the exceptions in the first case, or finding a way to use the token to make a programmatic SDK API call in the second.
What can I try next?
Edit 1
I'm trying with the next approach, but the same exception is thrown:
var accessToken = GetToken();
var client = new Graph.GraphServiceClient(
new Graph.DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}));
var mails = client.Users[authMail].MailFolders.SentItems.Messages.Request().GetAsync().Result;
Pls go to the api document to check the api permission required. For example, this api required Mail.ReadBasic.All, Mail.Read, Mail.ReadWrite for application type. My code sample requires to use application type of api permission.
Pls consent the api permission and try code below:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "aad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var res = await graphClient.Users["{user-id}"].MailFolders.SentItems.Request().GetAsync();
In your first example you trying to use IWA auth https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token-integrated-windows-authentication?tabs=dotnet and it failing because there is interaction required. Most likely this is due to MFA being enabled on the account, generally you don't want to disable MFA so you either need to deal with the interaction and perform the other factor or use another method. You also don't have the correct scope for email eg Mail.Read would be required for
In the second method your using client credentials flow (but the older v1 flow) but if you want to use the Graph SDK it easier to just do https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#client-credentials-provider but make sure you have the correct permission in your app registration and make sure its been consented to.

Unable to fecth all user details using Microsoft Graph api graphServiceClient.Users.Request() C# [duplicate]

This question already has answers here:
Microsoft Graph api code in C# displays only limited number of users
(2 answers)
Closed 3 years ago.
I am running below code :
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System;
namespace MSGraphAPI
{
class Program
{
private static string clientId = "XXXXX";
private static string tenantID = "XXXX";
private static string objectId = "XXXX";
private static string clientSecret = "XXXX";
static async System.Threading.Tasks.Task Main(string[] args)
{
// IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
//.Create(clientId)
//.WithTenantId(tenantID)
//.WithClientSecret(clientSecret)
//.Build();
// ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
// GraphServiceClient graphClient = new GraphServiceClient(authProvider);
// var users = await graphClient.Users
// .Request()
// .GetAsync();
var tenantId = "XXXX.onmicrosoft.com";
// The client ID of the app registered in Azure AD
var clientId = "XXXX";
// *Never* include client secrets in source code!
var clientSecret = "XXXX"; // 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/XXXXX.onmicrosoft.com/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 System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
// Make a Microsoft Graph API query
var users = await graphServiceClient.Users.Request().GetAsync();
Console.WriteLine(users.ToString());
IGraphServiceUsersCollectionPage userss = graphServiceClient.Users.Request().GetAsync().Result;
foreach (User user in userss)
{
Console.WriteLine("Found user: " + user.DisplayName);
}
}
}
}
I am getting only 100 users ( when i use debug and check the count and also put a watch on this variable) in the userss variable , but total number of users is around 1000 . I want to fetch all user details and also want to fetch users based on specific criteria using select or any other api in the same code.
Please help me.
Below code works to show all user details.
do
{
foreach (User user in users)
{
if (user.Mail != null)
{
if (user.Mail.Contains("Tom"))
{
Console.WriteLine("Hurray");
}
}
Console.WriteLine($"{user.Id}");
Flag++;
}
}
while (users.NextPageRequest != null && (users = await users.NextPageRequest.GetAsync()).Count > 0);

UnAuthorized issue when request user list to Microsoft Graph by using HttpClient

I am trying to get information about users from Microsoft Graph via
https://graph.microsoft.com/v1.0/users.
It's returning a 401 - Unauthorized:
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure. Invalid audience.",
"innerError": {
"request-id": "3157d513-6f31-4d2d-a3d7-a97eed7207ba",
"date": "2019-12-11T05:39:02"
}
}
}
My code:
AuthenticationContext authContext =
new AuthenticationContext(string.Format(CultureInfo.InvariantCulture,
"https://login.microsoftonline.com/{0}", "my-domain name"));
ClientCredential clientCred =
new ClientCredential("Client-id", "Client-Secret-id");
AuthenticationResult authenticationResult = authContext
.AcquireTokenAsync("https://graph.windows.net", clientCred).Result;
var token = authenticationResult.AccessToken;
var client = new HttpClient();
var uri = "https://graph.microsoft.com/v1.0/me/";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(token);
var response = await client.GetAsync(uri);
Where I did go wrong? Why I am not getting a proper access token? Could anyone please help me to use the MS Graph?
You use the wrong resource, you need to get the token for Microsoft Graph instead of AAD Graph,
it should be https://graph.microsoft.com, not https://graph.windows.net.
AuthenticationResult authenticationResult = authContext.AcquireTokenAsync("https://graph.microsoft.com",
clientCred).Result;
Update:
Make sure you grant the User.Read.All Application permission.
Then try the code as below, it works on my side.
using System;
using System.Net.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
string _authString = "https://login.microsoftonline.com/xxxxxx.onmicrosoft.com";
string _clientId = "<client-id>";
string _clientSecret = "<client-secret>";
AuthenticationContext authenticationContext = new AuthenticationContext(_authString, false);
ClientCredential clientCred = new ClientCredential(_clientId, _clientSecret);
AuthenticationResult authenticationResult;
authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCred).GetAwaiter().GetResult();
Console.WriteLine(authenticationResult.AccessToken);
var token = authenticationResult.AccessToken;
var client = new HttpClient();
var uri = "https://graph.microsoft.com/v1.0/users";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
client.DefaultRequestHeaders.Accept.Clear();
//GET Method
HttpResponseMessage response = client.GetAsync(uri).GetAwaiter().GetResult();
Console.WriteLine(response.Content.ReadAsStringAsync().Result.ToString());
}
}
}
I think if you're calling Microsoft Graph the resource needs to be https://graph.microsoft.com instead of AAD Graph (graph.windows.net). Can you try changing that in your AcquireTokenAsync call?
There are two issues :
Wrong resource , the resource should be https://graph.microsoft.com . And confirm that you have grant correct Microsoft Graph's permissions in Azure AD portal
You are using client credential flow as using AcquireTokenAsync(String, ClientCredential) method without user , so https://graph.microsoft.com/v1.0/me/ won't work since there is no user in it . Use GET /users/{id | userPrincipalName} instead . Also , you should grant Application Permission in azure portal since you are using M2M flow .
Permissions (from least to most privileged) :
Application :User.Read.All, User.ReadWrite.All, Directory.Read.All, Directory.ReadWrite.All
Make sure the account you're using while making the Graph API calls has the Required Permissions. As you're invoking a GET call,
Below permissions should be set up.
More about permissions here: https://learn.microsoft.com/en-us/graph/permissions-reference
The Error posted clearly states that the account you're using to make calls to GRAPH API is unauthorized. Have the permissions set right and the access token will be generated and will be authenticated against your application.
EDIT: Try the below code to get a valid access token.
static string AppID = "<Your Application ID>";
static string APPKey = "<Your Application Key>";
static string tenantId = "<Your ORG Tenant ID>";
static string RedirectURI = "<Your Application's custom Redirect URI>";
static string GraphApi = "https://graph.microsoft.com/v1.0/"
public static IAuthenticationProvider CreateAuthorizationProvider()
{
var authority = $"https://login.microsoftonline.com/{tenantId}/v2.0";
List<string> scopes = new List<string>();
scopes.Add("https://graph.microsoft.com/.default");
var cca = ConfidentialClientApplicationBuilder.Create(AppID)
.WithAuthority(authority)
.WithRedirectUri(RedirectURI)
.WithClientSecret(APPKey)
.Build();
return new MsalAuthenticationProvider(cca, scopes.ToArray());
}
public static HttpClient GetAuthenticatedHTTPClient()
{
var authenticationProvider = CreateAuthorizationProvider();
_httpClient = new HttpClient(new AuthHandler(authenticationProvider, new HttpClientHandler()));
return _httpClient;
}
private static async Task<User> GetADUserInfo(HttpClient client,string email)
{
User user = new User();
client = GetAuthenticatedHTTPClient();
client.BaseAddress = new Uri(GraphApi);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
WriteToConsole("Call Graph API :: retrieving AD Info for the employee ::" + email);
using (client)
{
try
{
HttpResponseMessage res = await client.GetAsync("users/" + email);
res.EnsureSuccessStatusCode();
if (res.IsSuccessStatusCode)
{
user = await res.Content.ReadAsAsync<User>();
WriteToConsole("Call Graph API :: Call Success for employee ::" + email);
}
}
catch (Exception ex)
{
LogError(ex, "Error in Getting AD User info via Graph API");
return null;
}
return user;
}
}
The Above code uses MSALAuthentication, Use the code below :
public class MsalAuthenticationProvider : IAuthenticationProvider
{
private IConfidentialClientApplication _clientApplication;
private string[] _scopes;
public MsalAuthenticationProvider(IConfidentialClientApplication clientApplication, string[] scopes)
{
_clientApplication = clientApplication;
_scopes = scopes;
}
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var token = await GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
public async Task<string> GetTokenAsync()
{
AuthenticationResult authResult = null;
authResult = await _clientApplication.AcquireTokenForClient(_scopes).ExecuteAsync();
return authResult.AccessToken;
}
}
AuthHandler Class :
public class AuthHandler : DelegatingHandler
{
private IAuthenticationProvider _authenticationProvider;
public AuthHandler(IAuthenticationProvider authenticationProvider, HttpMessageHandler innerHandler)
{
InnerHandler = innerHandler;
_authenticationProvider = authenticationProvider;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await _authenticationProvider.AuthenticateRequestAsync(request);
return await base.SendAsync(request, cancellationToken);
}
}
You have a few issues going on:
You should be requesting a token for https://graph.microsoft.com, not https://graph.windows.net. The graph.windows.net is the older AAD Graph, not the newer Microsoft Graph:
AuthenticationResult authenticationResult = authContext
.AcquireTokenAsync("https://graph.windows.net", clientCred).Result;
You cannot use /me with the Client Credentials grant. Graph translates /me into /users/{currently authenticated user id}. Since you're not authenticating a user, the "currently authenticated user id" is null:
var uri = "https://graph.microsoft.com/v1.0/users/user#domain.onmicrosoft.com";
You are setting the Authorization header's value but not the scheme. You need to set both:
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
It isn't clear from your question which scopes you've requested or if you've received Admin Consent. You need to make sure you've requested the Application scope User.Read.All and received Admin Consent from a tenant administrator.

Pass token in header for authentication in MVC and Web API

Integrating MVC app with Web API, Azure Users Authentication is done using OWIN, Want to remove authentication cookie and pass token in header for api call. how to do it? I use MSAL.cs file for Azure AD authentication. Want to pass token in api call header. first load MVC application page, after authentication call web api methods.
I used following code for azure AD autherization,
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
After the first time sign in users from azure ad using the ASP.Net OpenID Connect OWIN middleware , if you want to call web api , you can add the token to request header :
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string authority = String.Format(CultureInfo.InvariantCulture, Startup.aadInstance, tenantID, string.Empty);
ClientCredential credential = new ClientCredential(Startup.clientSecret);
// Here you ask for a token using the web app's clientId as the scope, since the web app and service share the same clientId.
app = new ConfidentialClientApplication(Startup.clientId, redirectUri, credential, new NaiveSessionCache(userObjectID, this.HttpContext)){};
result = await app.AcquireTokenSilentAsync(new string[] { Startup.clientId });
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, serviceUrl + "/api/todolist");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.Token);
HttpResponseMessage response = await client.SendAsync(request);
Please refer to code sample for more details .

Categories

Resources