Microsoft.Identity.Web OpenIdConnect was not authenticated. Failure message: Not authenticated - azure-active-directory

My app had been working with Microsoft.Identity for the past couple of years and something changed that is causing users to have to clear cache to authenticate. I have verified reply url's, the tenant id, client id and client secret and they all have not changed and are correctly setup. I updated to the latest nuget package. And after clearing browser cache the authentication works perfectly. What could be causing this?
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.HandleSameSiteCookieCompatibility();
});
services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd");
//services.AddSignIn(Configuration);
// Token acquisition service based on MSAL.NET
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthentication();
app.UseSession();
app.UseMiddleware<UserClaims>();
app.UseAuthorization();
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request starting HTTP/2.0 POST https://localhost:44326/signin-oidc application/x-www-form-urlencoded 3217
Microsoft.AspNetCore.Cors.Infrastructure.CorsService: Information: CORS policy execution successful.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler: Information: AuthenticationScheme: Cookies signed in.
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished in 626.0063ms 302
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request starting HTTP/2.0 GET https://localhost:44326/
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: Information: OpenIdConnect was not authenticated. Failure message: Not authenticated
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Information: Authorization failed.
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: Information: AuthenticationScheme: OpenIdConnect was challenged.
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished in 225.1113ms 302

Please make sure to add authentication middleware in order that app.UseAuthentication(); comes before app.UseMvc();
app.UseAuthentication();
app.UseAuthorization();
Note that AddMicrosoftIdentityWebApp is using its own cookie (auth) provider
instead using the existing one (maybe After being upgraded).
Try using by making cookiescheme to null
services.AddAuthentication() .AddMicrosoftIdentityWebApp(Configuration,"AzureAd", "AzureAD", cookieScheme: null);
or
try to explicity add the schemes so that scheme is able to handles exceptions of challenge errors like 401.
For that you may need to call AddAuthentication() and configure a default challenge schemes something like this.
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
NOTE: AddAuthentication() method actually configures the service to add cookie-based authentication.This cookie used in browser scenarios and also to set the challenge to OpenID Connect.
and register Microsoft Identity routes with one call minumum to endpoints.MapControllerRoute() or a call to endpoints.MapControllers().
or
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
});
Please check microsoft-identity-web issues in github
References:
aspnetcore-webapp-openidconnect-v2 (github.com)
asp.net mvc - IdentityServer4 - Stack Overflow
Stack Overflow reference

Related

Azure Authentication from client app to another API

I'm trying to get azure AD authentication working between a Blazor WASM app, and another API that I have running locally but on a different port. I need both applications to use the Azure login, but I only want the user to have to log in once on the Blazor app which should then pass those credentials through to the API.
I've set up app registrations for both apps in the portal, created the redirect url, exposed the API with a scope and I can successfully log into the blazor app and see my name using #context.User.Identity.Name.
When it then tries to call the API though, I get a 401 error back and it doesn't hit any breakpoints in the API (presumably because there is no authentication being passed across in the http request).
My code in the Blazor app sets up a http client with the base address set to the API:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("APIClient", client => client.BaseAddress = new Uri("https://localhost:11001"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("APIClient"));
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://d3152e51-9f5e-4ff7-85f2-8df5df5e2b2e/MyAPI");
//options.UserOptions.RoleClaim = "appRole";
});
await builder.Build().RunAsync();
}
In my API, I just have the Authorise attribute set on the class, and eventually will need roles in there too:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class CarController
Then, in my Blazor component, I then inject the http factory and try to make a request:
#inject IHttpClientFactory _factory
...
private async Task RetrieveCars()
{
var httpClient = _factory.CreateClient("APIClient");
HttpResponseMessage response = await httpClient.GetAsync("https://localhost:11001/api/cars");
var resp = await response.Content.ReadAsStringAsync();
cars = JsonSerializer.Deserialize<List<Car>>(resp);
}
but this returns the 401 error. I've also tried a few different variations like just injecting a http client (#inject HttpClient Http) but nothing seems to be adding my authorisation into the API calls. The options.UserOptions.RoleClaim is also commented out in the AddMsalAuthentication section as I wasn't sure if it was needed, but it doesn't work with or without it in there.
Can anyone explain what I'm doing wrong and what code I should be using?
Common causes.
Most cases ,we tend to forget to grant consent after giving API
permissions in the app registration portal,after exposing the api
which may lead to unauthorized error.
Other thing is when Audience doesn’t match the “aud” claim when we
track the token in jwt.io .Make sure ,Audience=clientId is configured
in the code in authentication scheme or Token validation parameters
by giving ValidAudiences.And also try with and without api:// prefix
in client id parameter.
Sometimes aud claim doesn’t match as we mistakenly send ID token
instead of Access tokens as access tokens are meant to call APIs .So
make sure you check mark both ID Token and access token in portal
while app registration.
While Enabling the authentication by injecting the [Authorize]
attribute to the Razor pages.Also add reference
Microsoft.AspNetCore.Authorization as(#using
Microsoft.AspNetCore.Authorization)
Please see the note in MS docs and some common-errors
If above are not the cases, please provide with additional error details and startup configurations or any link that you are following to investigate further.

IdentityServer4 with ADFS as external Identity Provider

I'm having a ASP.NET MVC test app who should work as an implicit OIDC client having access and id tokens from an IdentityServer4 app (both are dotnet core 3.1). IdSvr has a couple of external OIDC IdPs configured: A KeyCloak instance, and a ADFS (4.0) ditto.
My IdSvr configuration of ADFS is as follows:
.AddOpenIdConnect("oidc_adfs", "ADFS", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.Authority = "<<ADFS endpoint>>";
options.ClientId = "<<ADFS defined client id>>";
options.ClientSecret = "<<ADFS defined client secret>>";
options.Resource = "<<My resource identifier in ADFS>>";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
options.ResponseType = "id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
});
In the KeyCloak case, everything goes fine - the callback request to IdSvr's "/signin-oidc" goes fine and the front-channel user agent ends at the destination test app's post-auth endpoint and the tokens are available.
When I use ADFS, the flow stops with a HTTP 500 after the user is authenticated in ADFS, and the "/signin-oidc" endpoint is hit, and IdSvr log reads:
CORS request made for path: /signin-oidc from origin:
<<ADFS_endpoint>> but was ignored because path was not for an
allowed IdentityServer CORS endpoint 2020-09-20 12:34:01.157 +02:00
[INF] Error from RemoteAuthentication: Unable to unprotect the
message.State..
I've setup CORS according to the IdentityServer4 docs, so the problem might be something else?
When inspecting differences in KeyCloak and ADFS callback requests to "/signin-oicd", I can see that ADFS does add Referer/Origin to the request and KeyCloak does not. Apart from that, the two requests seem quite similar.
Hope someone can help.
If you see the Origin header from ADFS, then I guess you need to add the ADFS domain to the list of allowed CORS endpoints.
What error is shown in the ADFS event log?
The problem may also be that ADFS 4.0 does not support CORS?

Blazor WASM - AzureAD Auth - HttpContext.User.Claims are Empty?

I am trying to switchover to Azure AD for my identity platform on a Blazor WASM app I am building. I have followed this documentation from Microsoft incredibly closely.
When I log in to the app, the client app is able to display the logged in user's name, which comes from the AuthenticationState object.
However, on the server side, when I sent a an HTTP request (POSTing a comment for example), the HttpContext.User.Claims are empty, and the following line I was using previously to get the userId returns null:
comment.UserId = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
I plead some ignorance on Claims/Identitys/Microsoft Graph etc., but I still don't know understand why the user / claims would be unknown given this information was accessible at least at one point since the Client App was able to display the user's name...
I've also looked at the following StackOverflow/GitHub posts, but have not found anything that has solved this issue:
Blazor with AzureAD Auth, Context.Identity.User.Name is null
Out of box AAD B2C not grabbing user.identity.name
User.Identity.Name empty when implementing Azure B2C in Blazor WebAssembly
What am I missing here to be able to identify the user on the incoming HTTP requests?
FWIW, here is my Startup.cs class (with some irrelevant code emitted):
public void ConfigureServices(IServiceCollection services)
{
#region Azure Active Directory
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => {
Configuration.Bind("AzureAd", options);
}
);
#endregion
services.Configure<JwtBearerOptions>(
AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.TokenValidationParameters.NameClaimType = "name";
//options.TokenValidationParameters.NameClaimType = "preferred_username";
});
services.AddHttpContextAccessor();
services.AddControllersWithViews().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddRazorPages();
}
And Configure():
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
The issue was that removing/not having the "api://" scheme prepended to the client ID caused this to not populate the claims for some reason. Very odd. .

Ocelot Integration with Azure Active Directory Authentication .Net Core 3.1

Context: My company is moving to an API Gateway to manage all our or services in our network. Each service is currently authenticated using azure AD. Everything is single tenant and just allows company users.
Question: Am I understanding this correctly?
Problem: When I call http:localhost:5000/authResource/get I get a 401 response back from Ocelot. After tracing through the error, I see that I get null back when the Ocelot Authentication middle ware attempts to authenticate using the AzureADJwtBearer scheme.
I followed the advice from questions related to Ocelot and azure ad but even following that I am unable to get anything to work. (Mostly from this answer: How set up Ocelot Api Gateway with Azure Active Directory)
I think I may be misunderstanding how the authentication is supposed to work. My current understanding is that I tell Ocelot to authenticate against one of my Apis using the AzureADJwtBearer scheme. In my configuration I have the information for that specific api set up (ClientId, TenantId, etc).
Once I call the route, I am expecting Ocelot to make a call to Microsoft to start authentication. At this point, I expect the same flow as the Apis provide, i.e. I make the call and get the Microsoft login page which then redirects me back to the application once I enter my username and password.
After redirecting me back to Ocelot I guess (this is the part I am fuzzy on), I expect ocelot to store the access token Microsoft sends back FOR THE SPECIFIC RESOURCE I JUST REQUESTED. Then I expect ocelot to attach the token to an Auth header and then send the request for the resource I initially asked for.
To clarify this, I will include the code I have for my startup files, and my ocelot.json file.
From Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddProtectedWebApi(Configuration)
.AddProtectedApiCallsWebApis(Configuration)
.AddInMemoryTokenCaches();
services.AddOcelot(Configuration);
services.AddControllers();
}
Also from Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseOcelot().Wait();
app.UseAuthentication();
}
My ocelot.json file for the authenticated resource is as follows (names changed for security reasons):
{
"DownstreamPathTemplate": "/api/Controller/Get",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5003
}
],
"UpstreamPathTemplate": "/authResource/get",
"UpstreamHttpMethod": [ "GET" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "AzureADJwtBearer",
"AllowedScopes": []
}
}
To solidify how I understand this, I will go through an example using the sample api shown in the ocelot configuration.
I wish to access resource http://localhost:5003/api/Controller/Get which is a protected API meaning I can only get a response from this if I provide an authorization header with my get request.
I make a request through my ocelot gateway for the url http://localhost:5000/authResource/get (I am hosting Ocelot on localhost:5000).
Ocelot sees it needs to authenticate to access this resource, so it makes a request using the AzureADJwtBearer scheme.
I am redirected to microsoft to login. Once done, I am sent back to the Ocelot application with an access token in tow.
Ocelot takes this taken, creates the Auth header, and finally calls http://localhost:5003/api/Controller/Get and returns the result.
Working Example for .Net Core 3.1
I ended up getting this working using the Microsoft.Identity.Web library (current version located at https://github.com/AzureAD/microsoft-identity-web/tree/master/src/Microsoft.Identity.Web)
First up, my ocelot configuration file (ocelot.json):
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/myapp/api/{everything}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "apphost.com",
"Port": 443
}
],
"UpstreamPathTemplate": "/api/{everything}",
"UpstreamHttpMethod": [ "GET", "POST" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": []
}
}
]
}
Notice the AuthenticationProviderKey has the value Bearer.
My appsettings.json file contains my azure configurations:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mydomain.com",
"TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
Then finally, my Startup.cs file contains something like this (reduced for brevity)
public void ConfigureServices(IServiceCollection services)
{
services.AddProtectedWebApi(Configuration)
.AddProtectedApiCallsWebApis(Configuration)
.AddInMemoryTokenCaches();
services.AddOcelot(Configuration);
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseOcelot().Wait();
app.UseAuthentication();
}
With this all set up, I can pass in a bearer token generated using my azure credentials, and the ocelot gateway will correctly validate it.

Hosting asp.net core + ReactJS web app with SSL containing multiple CN or domain names is causing invalid issuer error

I am facing the following problem while hosting a web app built with asp.net core 3.1 and React.
We have used default visual studio template for React. ASP.NET Identity is used for authentication and authorization.
Authentication and Authorization work as expected as long as we host the website with an SSL certificate issued for single domain or CN. (e.g. example.com)
If we host he website with an SSL with multiple CNs (e.g. example.com, sub1.example.com, sub2.example.com), it works fine for any ONE of the domains. For the remaining domains we get the following behavior:
The login works as expected. The /connect/token path issues valid token. Once logged in, when we try to invoke any api (all apis are hosted under /api route), we get 401 unauthorized error. Error description in the header:
WWW-Authenticate: Bearer error="invalid_token", error_description="The issuer 'https://sub1.example.com' is invalid".
I also tried parsing the issued token on jwt.io. The iss field (issuer) is https://sub1.example.com which exactly matches the error description. I cannot fathom why identity engine refuses to identify the issuer for which it issued token for.
Here is relevant snippet from Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
}
Any ideas?
The new .Net (.net core) is highly configurable and modular. Usually the extension methods take a delegate which we can use to configure options. However, AddIdentityServerJwt method doesn't follow that convention.
I noticed long time ago that there is a property called ValidIssuers in TokenValidationParameters which can be configured with AddJwtBearer extension method. However, AddIdentityServerJwt extension method doesn't accept any options delegate as parameter.
It turns out that there is a special way to configure options.
services.AddAuthentication()
.AddIdentityServerJwt();
services.Configure<JwtBearerOptions>(IdentityServerJwtConstants.IdentityServerJwtBearerScheme, options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidIssuers = new string[] { "https://sub1.example.com", "https://sub2.example.com", "https://sub3.example.com" }
};
});
Added this code and problem solved. Configuration can also be moved to appsettings.json.
This is probably happening as a result of receiving the token from an instance of IdentityServer4 on one CN, and trying to validate it with a request to IdentityServer4 using another CN. The IdentityServer component that's rejecting the token is TokenValidator's ValidateJwtAsync method. This method passes in the issuer into JwtSecurityTokenHandler's ValidateToken as a property of TokenValidationParameters. The issuer is retrieved from either the issuer configured on the IdentityServerOptions in the 'AddIdentityServer' extension method, or is dynamically generated from the request.
I can think of one way to resolve the validation problems, and that is to set the issuer on the IdentityServerOptions using the delegate passed into AddIdentityServer. This will result in the same issuer being set for all tokens issued, regardless of the CN it was accessed from. This would allow IdentityServer a single source of truth for issuer information, and will allow IdentityServer to know which issuer to verify against when a token comes in for validation.
Other solutions of trying to maintain the issuer are heavily restricted by the TokenValidator being an internal class that can't be inherited and easily replaced with an implementation that will validate against a list of valid issuers. Additionally, the IdentityServerOptions that's configured to have the issuer uri is registered as a singleton and cannot have its values changed. Other contrived implementation could be devised like attempting to dynamically change the host value on the HttpContext with a middleware (which I'm not sure is even possible since I've never tried), but anything that goes against IdentityServer4's design decision is not advised.
Please check url http://{url}/.well-known/openid-configuration
This url is should be true
Following codes are worked different domain.
Auth Startup
services.AddIdentityServer(options =>
{
options.IssuerUri = Configuration["ServerSettings:Authority"].ToString();
options.PublicOrigin = Configuration["ServerSettings:Authority"].ToString();
})
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients())
.AddProfileService<ProfileService>();
Api Startup
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration["ServerSettings:Authority"].ToString(); //"http://localhost:31864";
options.RequireHttpsMetadata = false;
options.ApiName = "api";
});
Works in the same domain but if different domain you should specify this

Resources