I have a .NET blazor wasm app that is using an api endpoint that is built into the solution , and trying to make use of the ms graph api too.
I have two named clients one for the app api and another for the graph api.
My Program.cs file looks like this (Blazor WASM .NET 6 hosted model).
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("App.ServerAPI"));
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("App.Graph"));
builder.Services.AddHttpClient("App.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddHttpClient("App.Graph", client => client.BaseAddress = new Uri("https://graph.microsoft.com/"));
I then go onto configure the Auth Layer like this . The graph api does not need access to the app api. My server api does not call the graph api directly.
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://xxx-xxx-xxx-xx/API.Access");
});
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, RemoteUserAccount>(options =>
{
var scopes = builder.Configuration.GetValue<string>("GraphScopes");
if (string.IsNullOrEmpty(scopes))
{
Console.WriteLine("Warning : No scope permissions founds.");
scopes = "User.Read";
}
foreach (var scope in scopes.Split(";"))
{
global::System.Console.WriteLine($"Adding {scope} to requested permissions");
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
}
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, GraphUserAccountFactory>();
I used this example to extend/create the graph api Named client
https://learn.microsoft.com/en-us/graph/tutorials/blazor
I can authenticate with each of the named clients when testing seperately, and make successful calls against both api's, but when I configure the app for capabilites to call both api's and call the ms graph api I get the following error.
Graph api Response: InvalidAuthenticationToken\nMessage: Access token validation failure. Invalid audience.
I do not have any errors on the login/auth process with both of the items listed as above.
I've looked at configuring multiple auth schemes for .NET 6 as suggested here - https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-6.0#use-multiple-authentication-schemes
, but the namespace is not recognized in WASM when trying to configure JWT , and the app fails to build when trying this.
I believe the Audience needs to be configured, but I am unware of how to properly achieve this in .NET 6.
Any advice on how to go about properly utilizing two named clients within one solution ? Or how to properly configure authority within the scope of the graph client ? Thanks
Related
I'm trying to wrap my head around the "modern" auth methods, and dealing with OAuth and access token for calling external services, in ASP.NET Core 5 MVC.
I have an app registration in Azure, which is set up OK - these are the API permissions for that app:
My goal is to call both MS Graph (several calls), and also MS Dynamics365, to gather some information. I've managed to set up authentication with OAuth (or is it OpenID Connect? I'm never quite sure how to tell these two apart) in my MVC app, like this (in Startup.cs):
/* in appsettings.json:
"MicrosoftGraph": {
"Scopes": "user.read organization.read.all servicehealth.read.all servicemessage.read.all",
"BaseUrl": "https://graph.microsoft.com/v1.0"
},
*/
public void ConfigureServices(IServiceCollection services)
{
List<string> initialScopes = Configuration.GetValue<string>("MicrosoftGraph:Scopes")?.Split(' ').ToList();
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddInMemoryTokenCaches();
// further service setups
}
It works fine - I'm prompted to log in, provide my credentials, and in my MVC app, I can check out the claims principals with its claims after logging in - so far, everything seems fine.
Next step is calling the downstream APIs. I studied some blog posts and tutorials and came up with this solution to fetch an access token, based on the name of a scope that I need for a given call. This is my code here (in a Razor page, used to show the data fetched from MS Graph):
public async Task OnGetAsync()
{
List<string> scopes = new List<string>();
scopes.Add("Organization.Read.All");
// fetch the OAuth access token for calling the MS Graph API
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
HttpClient client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Add("Accept", "application/json");
string graphUrl = "https://graph.microsoft.com/v1.0/subscribedSkus";
string responseJson = await client.GetStringAsync(graphUrl);
// further processing and display of data fetched from MS Graph
}
For the MS Graph scopes, this works just fine - I get my access token, I can pass that to my HttpClient and the call to MS Graph succeeds and I get back the desired info.
The challenge starts when trying to use the same method for getting an access token to call MS Dynamics. I was assuming that I just specify the name of the API permission that is defined in the Azure AD registration - user_impersonation - like this:
public async Task OnGetAsync()
{
List<string> scopes = new List<string>();
scopes.Add("user_impersonation");
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
// further code
}
But now I am getting nothing but errors - like this one:
An unhandled exception occurred while processing the request.
MsalUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application with ID '257a582c-4461-40a4-95c3-2f257d2f8693' named 'BFH_Dyn365_Monitoring'. Send an interactive authorization request for this user and resource.
which is funny, because admin consent has been granted - so I'm not quite sure what the problem is .....
I then figured maybe I needed to add user_impersonation to the list of initial scopes (as defined in the appsettings.json and used in the Startup.ConfigureServices method) - but adding this results in another funny error:
An unhandled exception occurred while processing the request.
OpenIdConnectProtocolException: Message contains error: 'invalid_client', error_description: 'AADSTS650053: The application 'BFH_Dyn365_Monitoring' asked for scope 'user_impersonation' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor.
Strange thing is - as you saw in the very first screenshot here - that scope IS present on the app registration - so I'm not totally sure why this exception is thrown....
Can anyone shed some light, maybe from experience of calling MS Dynamics using an OAuth token? Is this just fundamentally not possible, or am I missing a step or two somewhere?
Thanks! Marc
To get a token for user_impersonation for Dynamics (instead of Microsoft Graph), you should use the full scope value: "{your CRM URL}/user_impersonation".
The full format for the values in the scopes parameter in GetAccessTokenForUserAsync is {resource}/{scope}, where {resource} is the ID or URI for the API you're trying to access, and {scope} is the delegated permission at that resource.
When you omit the {resource} portion, the Microsoft Identity platform assumes you mean Microsoft Graph. Thus, "Organization.Read.All" is interpreted as "https://graph.microsoft.com/Organization.Read.All".
When you attempt to request a token for "user_impersonation", the request fails because such a permission has not been granted for Microsoft Graph. In fact, such a permissions doesn't even exist (for Microsoft Graph), which explains the other error you see.
I am using Microsoft.AspNetCore.Components.Authorization to call my app registration and log in using my credentials. Everything works fine, but I can't figure out how to access my access token after logging in. I have an extremely simple app.
I followed the instructions here: Secure an ASP.NET Core Blazor WebAssembly standalone app with Azure Active Directory
I crteated the app and it looks pretty much exactly like the one on that page. However, I can't find any information on how to get the Token after logging in.
Thanks for any help.
To get the access token you need to inject IAccessTokenProvider and request the access token.
You may try the following, in a razor component...
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
#inject IAccessTokenProvider TokenProvider
var tokenResult = await TokenProvider.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
...
}
If you are using the default application, the token gets added to the HTTP client when the requests are being sent to the back end. This is done in the program.cs where the http client is created:
builder.Services.AddHttpClient("BlazorWasmWithAADAuth.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("BlazorWasmWithAADAuth.ServerAPI"));
if you are trying to get the token for calling another resource you will have to create another http client for that endpoint and add the token by creating a custom authorization message handler.
In ASP.NEt Zero, I am trying to integrate with Zapier using Identity Server 4. I managed to run it, however, what will authorize endpoint would be?
I am using .Net core with angular version where the login is happening in another web server. However, OAuth2 needs an authorization endpoint where the user authenticate, authoirze the app, and return a token.
I've assumed you're trying to get the endpoint on the server-side. To get any endpoint for your IDP, you need to read your IDP's discovery document, located at http://youridpurl.com/.well-known/openid-configuration. You can do this with the help of IdentityModel as follows:
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://youridpurl.com");
var authorizeEndpoint = disco.AuthorizeEndpoint;
See the IdentityServer4 and IdentityModel documentation for a more information.
I have a project that hosts the IdentityServer4 and I am attempting to also host in the same project a Web API, which accepts the access-token.
My question is, is possible that a single project contains the IdentityServer and an Web API that consume the same IdentityServer?
EDIT: The API must be secured with the Authorize attribute
I have an identity server 4 project, in the same project there is an API for CIUD of the clients. (Lets call it developer console api).
I then have a side project with is an asp .net core project that contains the actual razor pages for the Developer console it access the API within the Identity server project.
The reason i did it this way is that only one project should be updateing the database. So to update the database owned by the identity server it was decided the the API for accessing it should also be within the same project.
Yes you can have a web api from within your Identity server 4 project.
Configure service
services.AddAuthentication(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// base-address of your identityserver
options.Authority = settingsSetup.Settings.Authority;
// name of the API resource
options.ApiName = "testapi";
options.RequireHttpsMetadata = false;
});
Configure
I think it needs to have both of these.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseAuthentication();
app.UseIdentityServer();
Endpoints
Because the requests are sent using the access token as a bearer token then the authorize for each of the API calls needs to include the authencationScheme. I havent exactly figured out why but without this it doesnt work.
[HttpGet("Client/List")]
[Authorize(AuthenticationSchemes = "Bearer")]
public ActionResult ClientList()
{
}
While #DaImTo's answer is correct and working and it's developed by IdentityServer team, it uses Introspection Endpoint which means for every request AddIdentityServerAuthentication will create a http request and send it to your server, which is the same app.
I developed a library called IdentityServer4.Contrib.LocalAccessTokenValidation which do the exact same thing but without using Introspection Endpoint. It will authenticate the token directly from TokenStore which is configured in Services. You can use it if you are interested.
nuget link : https://www.nuget.org/packages/IdentityServer4.Contrib.LocalAccessTokenValidation
github link : https://github.com/Kahbazi/IdentityServer4.Contrib.LocalAccessTokenValidation
I am very new in implementing OAuth 2.0. I have implemented web-reference (ASMX file) in my xamarin project. Now I need to implement OAuth 2.0 for authentication. But I am not sure if ASMX supports OAuth2.0 or not. Do I need to implement WCF instead of Web refernce(ASMX) or is it possible in web reference (ASMX) to implement the OAuth2.0?
One possible solution is to add the token aquired from the authority to the Authorization header.
Your code could look similar to this:
var redirectURI = Windows.Security.Authentication.Web.WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
var _authContext = new AuthenticationContext(authority);
var tokenResult = await _authContext.AcquireTokenAsync(serviceResourceId, clientId, redirectURI);
if (tokenResult.Status != AuthenticationStatus.Success)
{
//Not authenticated
return;
}
var svc = new YourServiceReference.YourClient();
using (var scope = new OperationContextScope(svc.InnerChannel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[System.Net.HttpRequestHeader.Authorization] = tokenResult.AccessToken;
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
var result = svc.MyFunction();
//Do something with the data
}
Tricky - this is how mobile OAuth solutions work - but it is a big job:
Your Xamarin app would need to implement Mobile SSO to sign users in
Users of your Xamarin app would be redirected to login via an Authorization Server / Identity Provider after which they will receive an OAuth access token
After login your Xamarin App would then be able to call an API with the token and act on behalf of the user to get data
The API would need to validate received access tokens
SOLUTION PARTS
API: ASMX is an old technology so if you are working on the Microsoft stack you would use something newer such as a .Net Core Web API
MOBILE APP: The harder part of the solution will be implementing Mobile SSO, if you have not done so already.
How much of this solution exists already?