Microsoft Identity Web invalid token in Blazor WASM - .net-core

I am just finished trying to implement this: https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/1.%20Desktop%20app%20calls%20Web%20API
Instead of using a WPF application I am using a Blazor WASM client.
I added the token to the outgoing requests to my .net core API but I always get a 401.
Blazor authenticates well and gets the token back, seems to be working fine but:
the audience has the wrong GUID
"scp" (scope) is missing, hence the token being invalid for usage
If I run the sample from the link mentioned above and decode the token I can see a correct AUD & SCP in the token. So it's probably something with my configuration in Blazor?
Config in Blazor
// AD authentication
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://xxx-xxxx-xxxx-xxxx/access_as_user");
});
builder.Services
.AddHttpClient<IApiClient, ApiClient>(client => client.BaseAddress = _baseUri)
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
Response
Bearer error="invalid_token", error_description="The audience '63ee4227-xxxx-xxxx-xxxx' is invalid"
The audience GUID is the clientID of my Blazor app registration
Code in Startup.cs
...
services.AddProtectedWebApi(Configuration)
.AddInMemoryTokenCaches();
...
...
app.UseAuthentication();
app.UseAuthorization();
...
Any idea what could have been wrong?

When I was learning to use Blazor WebAssembly I followed this tutorial which does something very similar and it may be easier for you to implement as you don't have to try and convert any codefrom WPF.
However, one thing to try is to change this:
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://xxx-xxxx-xxxx-xxxx/access_as_user");
To this:
options.ProviderOptions.DefaultAccessTokenScopes.Add("xxx-xxxx-xxxx-xxxx/access_as_user");
In my application I am calling two API endpoints, one is hosted within the same domain as the Blazor application and the other is on a separate domain. When requesting a token for the external domain the api:// prefix must be used. However, for the same domain it must be excluded.

Related

Using Microsoft Authentication Library with External Providers (ASP.NET 6)

I have an Azure App Service developed in .NET 6. I wish to authenticate users against several providers (Azure AD, Google, etc). Upon following the (I think) accurate tutorial I was able to authenticate against Microsoft accounts using MSAL with the following code:
var builder = WebApplication.CreateBuilder(args);
// Managed identity credential
var credential = new DefaultAzureCredential();
// Add key vault for initial access
builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration.GetValue<string>("Azure:VaultUri")), credential);
// Integrate key vault into Azure app config
builder.Configuration.AddAzureAppConfiguration(options =>
{
// Load config string from vault
var configConnection = builder.Configuration.GetValue<string>("config");
// Connect to config
options.Connect(configConnection);
options.ConfigureKeyVault(kv => kv.SetCredential(credential));
});
// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// CUSTOM DI
builder.Services.AddSingleton<IDatabaseRepo, CosmosDatabaseRepo>();
// Build
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
The protected controller method is simply decorated with [Authorize].
That works great for Microsoft accounts. However, every attempt to integrate Google into this flow fails. I've done the following:
Follow various example utilizing the Microsoft.AspNetCore.Authentication.Google package. I added AddGoogle method with the client id and secret set. This results in an invalid token error.
The OAuth process works (using Postman) and provides both an access token and id token. But:
The access token fails with invalid_token
The open id token fails saying invalid_token and invalid signature.
If I remove AddMicrosoftIdentityWebApi and only use the AddGoogle method then I always receive a 500 Internal Server Error indicating an auth scheme was not defined. Doesn't matter where I define it- whether in AddAuthorization or decorated on the controller's Authorize attribute.
Attempted using manual JWT validation. This seems problematic because:
It also results in the 500 error whatever I try.
It's not supposed to be required if I understand the packages correctly.
ALSO:
The Google client id has been added to my App Service as an identity provider with the associated App (client) ID.
There's 38974 tutorials out there and none seem to point in the right direction/don't address this issue. Any and all guidance is appreciated.

Make requests to unpublished ASP.NET Core Web API for debugging

I'm developing an ASP.NET Core-based web app, but I'm currently in the debugging phase. This is also my very first time working with .NET Core.
I want to do application-level debugging and testing by making requests to the developed ASP.NET Core Web API from the frontend.
Can I publish the API somewhere for free for now to debug and test the entire web app?
Can I publish the API somewhere for free for now to debug and test the
entire web app?
You don't need to publish your API to test from your frontend app. You can can do that running your Web Api project locally therefore, setting debugger pointer to any break point.
Example Debugging Using Swagger In Localhost:
You could have a look on following example, I am testing the Web API project running on local environment using Swagger. I am sending request to my desired API and debugging at the same time whether the response expected. See below screenshot:
What if you encounter "Origin 'localhost:<port>' has been blocked by CORS policy" Error :
You could simply handel above error by following ways:
If you want to allow all request Endpoint:
builder.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true) // allow any origin
Note: Above code resolve your CORS error and allow all coming request from your frontend app
If you want to allow Certain request Endpoint:
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
policy =>
{
policy.WithOrigins("http://localhost:5094");
});
});
Then use as below:
app.UseCors(MyAllowSpecificOrigins);
Note: Above code allow the request only from the endpoint containing http://localhost:5094. URL
If you sitll need further information you could refer to our official document here

Blazor WebAssembly Standalone access multiple AAD protected APIs

I have managed to make default template work (my blazor standalone SPA should acquire tokens for several scopes from different ADApps - webAPIs; I've managed to get token only for one scope at the time even if I defined additionalScopes or defaultaccesstokenscopes).
builder.Services.AddMsalAuthentication(options =>
{
var config = options.ProviderOptions;
config.Authentication.Authority = "https://login.microsoftonline.com/tenantID";
config.Authentication.ClientId = "clientID";
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://graph.microsoft.com/user.read");
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://tenant.crm.dynamics.com/user_impersonation");
options.ProviderOptions.DefaultAccessTokenScopes.Add("clientID/scope1");
// tried this too
// config.AdditionalScopesToConsent.Add("https://tenant.crm.dynamics.com/user_impersonation");
});
Now there is a question on how to get the other tokens because it gets the token only for 'clientID' scope if multiple scopes are mentioned...? and use those tokens from wasm page in HttpClient request?
In angular (with MSAL) this is all done automatically, you define scopes you want and it gets all the tokens. Then it intercepts all requests and adds authorization header and corresponding token by domain of the request.
Is there similar mechanism here or should this be done manually by adding corresponding token for every request and using HttpRequestMessage with HttpClient.SendAsync()?
Obviously for business application there is not much of a use without contacting some kind of protected API, which is usually an app in the same AAD. For example let's say it can be a simple query to the Dynamics CRM's webapi.

Using Identity Server 3, ClaimsPrinciple null even after successful bearer token authentication

I have a test console app which I'm pointing at a local instance of Identity Server 3 to request an access token. The following code does this and returns my token fine (passing a single scope "scope.test.client").
static TokenResponse GetClientToken(string clientId, string clientSecret, string[] scopes)
{
var uri = new Uri(string.Concat(ID_BASE_URI, ID_URL_TOKEN));
var client = new TokenClient(
uri.AbsoluteUri,
clientId,
clientSecret);
return client.RequestClientCredentialsAsync(string.Join(" ", scopes)).Result;
I then use this token to call an API also running locally. This takes the TokenResponse obtained above and passed it to this method:
static void CallApi(string url, TokenResponse response)
{
try
{
using (var client = new HttpClient())
{
client.SetBearerToken(response.AccessToken);
Console.WriteLine(client.GetStringAsync(url).Result);
}
}
catch (Exception x)
{
Console.WriteLine(string.Format("Exception: {0}", x.Message));
}
}
The API (an ASP.NET WebApi project) uses an Owin Startup class to enforce bearer token authentication for all requests:
appBuilder.Map(baseApiUrl, inner =>
{
inner.UseWebApi(GlobalConfiguration.Configuration);
// Enforce bearer token authentication for all API requests
inner.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://identityserver/core",
ValidationMode = ValidationMode.ValidationEndpoint,
RequiredScopes = new[] { "scope.test.client" }
});
});
It also ensures all API requests are handled by a custom authorize attribute:
GlobalConfiguration.Configuration.Filters.Add(new DefaultApiAuthorizeAttribute());
Debugging this API, the first line in my overridden OnAuthorize method (in DefaultApiAuthorizeAttribute) is this:
var caller = actionContext.RequestContext.Principal as System.Security.Claims.ClaimsPrincipal;
If I break on this line I can see that actionContext.RequestContext.Principal is always null. However, I can see that ((System.Web.Http.Owin.OwinHttpRequestContext)actionContext.RequestContext).Request.Headers contains an Authorization header with the bearer token passed from my console app.
So it would seem that the API project is not authenticating the bearer token. Certainly the Identity Server logs suggest it isn't being hit at all after issuing the initial access token. So I'd appreciate your expert advice about why this might not be happening, or at least some pointers about where to look.
I suspect it might have something to do with SSL. Both sites are hosted locally under self-signed SSL certs, although Identity Server is configured to not require SSL and uses the idsrv3test.pfx development certificate for signing. I do have another test MVC web app which delegates authentication to the same IS3 instance which works fine locally, so I believe my IS3 instance is configured correctly.
You need to call UseIdentityServerBearerTokenAuthentication before you call UseWebApi. When you set up an OWIN Middleware Pipeline, the order is important.
In your case, Web API will be handling your requests before they get sent onto Identity Server (if they get sent on at all).
I imagine a range of possible issues could have the impact I described, but in my case I was able to find the cause by adding a diagnostics log to my consuming API. This led me to discover that the problem was an assembly conflict. The Owin middleware was looking for a Newtonsoft.JSON assembly with version 8.0.0.0 but my consuming API (actually running on top of a CMS intance) was using 7.0.0.0.
For anyone else who wants to find the answer fast, rather than spend hours tweaking configurations, here's the documentation that describes how to add this logging: https://identityserver.github.io/Documentation/docsv2/consuming/diagnostics.html

401 when calling UserInfo using ADFS 4.0 and OpenID Connect

I've successfully created a new Application Group with a Server Application as well as a Web API and the OpenID Connect protocol is working w/out any issues until I try and make a call to UserInfo. The Relying Party identifier is the same GUID as Client ID of the Server Application (per the examples I have read online). I get the error below when trying to call UserInfo:
WWW-Authenticate: Bearer error="invalid_token", error_description="MSIS9921: Received invalid UserInfo request. Audience 'microsoft:identityserver:21660d0d-93e8-45db-b770-45db974d432d' in the access token is not same as the identifier of the UserInfo relying party trust 'urn:microsoft:userinfo'."
Any help would be greatly appreciated.
I also recently got this error using ADFS with the ASP.NET Core OpenIDConnect providers. In my case, disabling the UserInfo request altogether resolved the issue:
var openIdOptions = new OpenIdConnectOptions
{
...
GetClaimsFromUserInfoEndpoint = false
};
After doing this, I still had the claims that I needed for my app - email, SID, name, etc. I'm sure there are scenarios where this would not work, but it's good to know you might not need /userinfo at all. I would still be interested in knowing why the token returned from ADFS can't be used to call /userinfo, and how to fix it in ASP.NET OpenIDConnect providers.
Just set the resource accordingly:
options.Resource = "urn:microsoft:userinfo";

Resources