Upgrading to MSAL Missing AUD in JWT token - c#

I am setting up a move to MSAL but the JWT token is missing the aud:
This is a WPF desktop app calling into a web api (ASP.Net)
.WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
.WithDefaultRedirectUri()
.Build();
I have set the scope to the to match the scope set up in the webapi (app registration)
string[] scopes = new string[] { "https://*******.azurewebsites.net/access_as_user" };
then call
var accounts = await App.PublicClientApp.GetAccountsAsync();
AuthenticationResult authResult;
try
{
authResult = await App.PublicClientApp
.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (Exception)
{
authResult = await App.PublicClientApp
.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}
The JWT token coming back always is missing the aud:
Then I call a simple test to see if the code will work.
var httpClient = new System.Net.Http.HttpClient();
System.Net.Http.HttpResponseMessage response;
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, "https://****.azurewebsites.net/api/Combo/getComboList");
//Add the token in Authorization header
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
I always get an error;
You do not have permission to view this directory or page.
Any pointer would be gratefully accepted.
Thanks
EDIT:
Here is a screen shot of the delegated permissions. On the Native client app under App Registration.

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.

Execute the Azure DevOps REST API using an access token

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

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.

Microsoft.Graph.ServiceException' Code: BadRequest Message: Current authenticated context is not valid for this request

I'm having trouble when using the Microsoft Graph API. Whenever I try to get a calendar, I get the following error message:
Exception thrown: 'Microsoft.Graph.ServiceException' in
System.Private.CoreLib.dll: 'Code: BadRequest Message: Current
authenticated context is not valid for this request
At first, I thought it was similar to this post, but my user is authenticated, so I believe it's not the case.
Here's my code:
EventController.cs
public async Task<Calendar> GetEventInfoAsync()
{
var accessToken = await getAcessTokenAsync();
DelegateAuthenticationProvider delegateAuthenticationProvider = new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}
);
GraphServiceClient graphClient = new GraphServiceClient(delegateAuthenticationProvider);
var calendar = await graphClient.Me.Calendar.Request().GetAsync();
return calendar;
}
And this is how I get the access token:
public async Task<string> getAcessTokenAsync()
{
if(User.Identity.IsAuthenticated)
{
var userId = User.FindFirst("MicrosoftUserId")?.Value;
ConfidentialClientApplication cca =
new ConfidentialClientApplication( Configuration["MicrosoftAuth:ClientId"],
String.Format(System.Globalization.CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}{1}", "common", "/v2.0"),
Configuration["MicrosoftAuth:RedirectUri"]+ "signin-oidc",
new Microsoft.Identity.Client.ClientCredential(Configuration["MicrosoftAuth:ClientSecret"]),
new SessionTokenCache(userId,_memoryCache).GetCacheInstance(),
null);
var token = await cca.AcquireTokenForClientAsync(new string[]{"https://graph.microsoft.com/.default"});
return token.AccessToken;
}
else
throw new Exception("User is not autenticated");
}
Finally, this is how the authentication options look in the startup file.
services.AddAuthentication().AddOpenIdConnect(openIdOptions =>
{
openIdOptions.ResponseType = OpenIdConnectResponseType.CodeIdToken;
openIdOptions.Authority = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}{1}", "common", "/v2.0");
openIdOptions.ClientId = Configuration["MicrosoftAuth:ClientId"];
openIdOptions.ClientSecret = Configuration["MicrosoftAuth:ClientSecret"];
openIdOptions.SaveTokens = true;
openIdOptions.TokenValidationParameters = new TokenValidationParameters{
ValidateIssuer = false
};
var scopes = Configuration["MicrosoftAuth:Scopes"].Split(' ');
foreach (string scope in scopes){
openIdOptions.Scope.Add(scope);
}
openIdOptions.Events = new OpenIdConnectEvents{
OnAuthorizationCodeReceived = async (context) =>
{
var userId = context.Principal.Claims.First(item => item.Type == ObjectIdentifierType).Value;
IMemoryCache memoryCache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
ConfidentialClientApplication cca =
new ConfidentialClientApplication( Configuration["MicrosoftAuth:ClientId"],
String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}{1}{2}", "common", "/v2.0", "/adminconsent"),
Configuration["MicrosoftAuth:RedirectUri"]+ "signin-oidc",
new Microsoft.Identity.Client.ClientCredential(Configuration["MicrosoftAuth:ClientSecret"]),
new SessionTokenCache(userId,memoryCache).GetCacheInstance(),
null);
var code = context.ProtocolMessage.Code;
var result = await cca.AcquireTokenByAuthorizationCodeAsync(code,new string[]{"User.Read.All", "Calendars.ReadWrite"});
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
},
};
});
My app is registered in the Microsoft Application Registration Portal, and I do get a token when I request for it, so I'm not sure what could possibly be causing the problem.
Same issue with the previews thread. There are two kinds of token issued by Azure AD, delegate for use or app. The token you were acquire is using the client credentials flow which is delegate for app. There is no me context when you request using this kind of token(refer Get access on behalf of a user and Get access without a user for the difference).
To integrate Microsoft Graph with web app and delegate the user to call the Microsoft Graph, you need to use the code grant flow(OnAuthorizationCodeReceived event) as you config in the startup.cs file.

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