Identityserve 4 in production env : "IDX10501: Signature validation failed. Unable to match key" - asp.net-core

I have a 3 tier .NET core application with:
identityserver 4
an API
a blazer app
On my local/dev computer, everything works fine. But, I have installed everything
into a real server and then I have an issue that appears.
I log into the application, play with, then I wait some time (don't know how much) and then when I try to use the app, the Blazor app crash because of this code :
bool isAuthenticated = await _authenticationVerifier.IsAuthenticatedAsync();
if (isAuthenticated)
User = await _userAppService.GetCurrentUserAsync();
The 'isAuthenticated' is true, in fact my cookies look good, but the Blazor app is no more authorized to connect to the API server.
I got the following error message on the API server:
Bearer was not authenticated. Failure message: IDX10501: Signature validation failed. Unable to match key:
kid: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
Exceptions caught: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
token: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
What is strange is that it work for some time, then after some time (1/2 days), I got this crash.
I don't know what to check and/or how to debug this issue. I'm looking for a solution since weeks :-(
I join some code:
On the API Server:
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = true;
options.ApiName = "MyAppName";
});
}
On the Blazor (Server side Blazor):
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(ApplicationConstants.LoginCookieExpirationDelay);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.ClientId = configuration["AuthServer:ClientId"];
options.ClientSecret = configuration["AuthServer:ClientSecret"];
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("role");
options.Scope.Add("email");
options.Scope.Add("phone");
options.Scope.Add("SoCloze");
options.ClaimActions.MapAbpClaimTypes();
});
context.Services.AddSingleton<BlazorServerAuthStateCache>();
context.Services.AddScoped<AuthenticationStateProvider, BlazorServerAuthState>();
context.Services.AddScoped<AuthenticationVerifier>();
}
On the IdentityServer 4 side:
// Identity cookie expiration
context.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = ".AspNetCore.Identity.Application";
options.ExpireTimeSpan = TimeSpan.FromDays(ApplicationConstants.LoginCookieExpirationDelay);
});
var clientConfig = context.Services.GetConfiguration().GetSection("IdentityServer:Clients");
context.Services
.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
options.RequireAuthenticatedSignIn = true;
})
.AddFacebook("Facebook", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.AppId = clientConfig["Facebook:ClientId"];
options.AppSecret = clientConfig["Facebook:ClientSecret"];
options.Fields.Add("picture");
})
.AddIdentityCookies();
}

Related

IdentityServer 4 WsFederation - How to get access token for calling API

I am using Identity Server 4 with the Ws-Federation plugin. Identity Server is configured to connect to Azure AD for authentication. Here is the relevant code from Identity Server project:
public void ConfigureServices(IServiceCollection services)
{
var rsaCertificate = new X509Certificate2("rsaCert.pfx", "1234");
services.AddRazorPages();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<User, IdentityRole>(options =>
{
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(2);
options.Lockout.MaxFailedAccessAttempts = 3;
})
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserStore<CustomUserStore>()
.AddUserManager<CustomUserManager>()
.AddDefaultTokenProviders();
services.AddTransient<IUserStore<User>, CustomUserStore>();
services.AddTransient<IEmailSender, EmailSender>();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddSigningCredential(rsaCertificate)
.AddInMemoryIdentityResources(IdentityConfig.IdentityResources)
.AddInMemoryApiScopes(IdentityConfig.ApiScopes)
.AddInMemoryClients(IdentityConfig.Clients)
.AddAspNetIdentity<User>()
.AddWsFederationPlugin(options =>
{
options.Licensee = "Licensee";
options.LicenseKey = "LicenseKey";
})
.AddInMemoryRelyingParties(new List<RelyingParty>());
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = "Azure AD App Id";
options.MetadataAddress = "WSFed metadata URL from Azure AD App";
options.Events.OnSecurityTokenValidated = SecurityTokenValidated;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(IdentityConfig.SessionTimeoutInMinutes);
options.SlidingExpiration = true;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
});
}
I have an API protected with JWT bearer authentication which is connected to the same Identity Server. The relevant code from the API (Please note that https://localhost:5001 is the address which the Identity Server is running in):
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
I have an MVC client as well which is connected to the same Identity Server. I was able to successfully authenticate users from the MVC client. Now, what I would like to do is to call a protected API endpoint in the API project from within the MVC client. I haven't found any way to get the access token necessary for calling the protected API. Relevant code from the MVC client:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = "aspnetcorewsfed";
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(Configuration.GetValue<int?>("SessionTimeoutInMinutes") ?? 15);
})
.AddWsFederation(options =>
{
options.MetadataAddress = "https://localhost:5001/wsfed"; // Address of the Identity Server
options.RequireHttpsMetadata = false;
options.Wtrealm = "mvc"; // ClientId registered in Identity Server
options.CallbackPath = "/";
options.SkipUnrecognizedRequests = true;
});
}
There's documentation on the Identity Server website that describes how to access protected APIs as seen here. But this is using OpenIdConnect. Since I am using WsFederation, I have no clue on how to get the access token or refresh token. Is token refresh impossible with WsFed?
Can anyone point me in the right direction on how to go about this?
Use OpenIdConnect in the MVC client instead of WsFed. Change the code in the MVC client's Startup.cs to the following:
services.AddAuthentication(options =>{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc-openid";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
});
The corresponding client registration in Identity Server should be:
new Client {
ClientId = "mvc-openid",
ClientSecrets = {
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {
"https://localhost:6001/signin-oidc"
},
AllowedScopes = new List < string > {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api"
}
}
https://localhost:5001 is the Identity Server address and https://localhost:6001 is the MVC client address.
The access token for accessing the API can be obtained like so:
var accessToken = await HttpContext.GetTokenAsync("access_token");

How do I get more recent userinfo with AddJwtBearer

We use IdentityServer to handle SSO authentication across our apps.
My application is an Aspnet core 3.0 website that passes the users Token to javascript. The javascript then calls a separate aspnet 2.2 API.
Problem: Logging a user out and back in does not update the ClaimsPrincipal on the API with new claims.
I have confirmed that the Web application has the new claims.
If I login Incognito or clear my cookies the new claim shows up in the API.
I am not sure where the responsibility for getting the claims should be and how to fix it. I assume the claims are part of the encrypted access_token, therefore I assume the Web app is sending a stale access_token to the API. So is the Web App what I need to fix? And what would be the proper fix?
Api Startup Code
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Bearer";
options.DefaultChallengeScheme = "Bearer";
})
.AddJwtBearer(options =>
{
options.Authority = oidcSettings.Authority;
options.Audience = oidcSettings.ApplicationName;
options.RequireHttpsMetadata = true;
});
Web App Startup Code
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.OnAppendCookie = cookieContext => { cookieContext.CookieOptions.SameSite = SameSiteMode.None; };
options.OnDeleteCookie = cookieContext =>
{
cookieContext.CookieOptions.SameSite = SameSiteMode.None; // this doesn't appear to get called.
};
});
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.SlidingExpiration = false;
options.ExpireTimeSpan = TimeSpan.FromHours(8);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = oidcSettings.Authority;
options.RequireHttpsMetadata = true;
options.ClientId = oidcSettings.ClientId;
options.ClientSecret = oidcSettings.ClientKey;
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("offline_access");
options.Scope.Add(oidcSettings.ApplicationName);
options.ClaimActions.MapJsonKey("role", "role"); // claims I am looking for are mapped here
options.Events.OnUserInformationReceived = async (context) =>
{
await Task.CompletedTask; // confirmed that after new sign in I can see updated info here.
};
});
TLDR: Javascript from Web app calls Api using access_token. When user logs out and logs back in, the API does not receive updated claims. I am not sure if the issue is the API needs to call out to identity server for user info or the Web App is not signing out properly and needs to send a fresh access_token?

Asp.net core 2.0+ - Multiple Authentication Schemes (Cookie / Bearer)

I've been struggling to get multiple authentication schemes working correctly in Asp.net core 2.1.
I am using Identity Server with an implicit flow and OpenIdConnect as the protocol.
When authorizing an action or controller with one of the schemes only (e.g Cookie or Bearer) the functionality works correctly.
Example:
[Authorize(AuthenticationSchemes = "Cookies")]
[Route("Cookies")]
public class BearerAndCookiesController : Controller {
If I however I specify on the Authorize attribute both schemes, then it fails partially. Bearer works as normal, but when I try to view the page in the browser it attempts to redirect to a local Login page (http://localhost/Account/Login).
When I inspect the debug logs of Identity Server nothing is returned, which makes sense as it hasn't attempted to contact the Authority. However when I look at the debug log of the Test MVC Site both the Bearer and Cookie schemes are challenged:
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:5002/cookies
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Get", controller = "BearerAndCookies"}. Executing action MvcClient.Controllers.BearerAndCookiesController.Get (MvcClient)
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes (Bearer, Cookies).
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: AuthenticationScheme: Bearer was challenged.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Cookies was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action MvcClient.Controllers.BearerAndCookiesController.Get (MvcClient) in 68.1922ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 93.2016ms 302
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:5002/Account/Login?ReturnUrl=%2Fcookies
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 30.2532ms 404
Failed to load resource: the server responded with a status of 404 (Not Found) [http://localhost:5002/Account/Login?ReturnUrl=%2Fcookies]
Does anyone know why this isn't working? I'll the person a beer! It's been hunting me for the last week.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-2.2&tabs=aspnetcore2x
Here is my Startup.cs configuration:
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options => {
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddJwtBearer(options => {
options.Authority = "http://localhost:5000";
options.Audience = "myApi";
options.RequireHttpsMetadata = false;
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options => {
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "myApi";
options.SaveTokens = true;
});
}
[Authorize(AuthenticationSchemes = AuthSchemes)]
[Route("Cookies")]
public class BearerAndCookiesController : Controller {
private const string AuthSchemes =
JwtBearerDefaults.AuthenticationScheme + "," +
CookieAuthenticationDefaults.AuthenticationScheme;
I wanted to give a better explanation of this answer:
I had to move services.AddAuthorization after the part were I
added both of the schemes. This ensures both schemes are registered
correctly.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options => {
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options => {
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "myApi";
options.SaveTokens = true;
}).AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options => {
options.Authority = "http://localhost:5000";
options.ApiName = "myApi";
options.RequireHttpsMetadata = false;
});
services.AddAuthorization(options => {
...
});
Then instead of specifying the Authorization Scheme as a part of the
Controller Action Authorize tag, I used a global policy when using
services.AddAuthorization
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
CookieAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
When I navigated to the any parts of the API it wouldn't redirect to the Login screen. I noticed that if you logged in first by navigating to Identity Server, then go back to that page it would actually authenticate you as normal. So I've put in what feel to be a little bit of hack. It is important that this directly goes in under the app.UseAuthentication.
app.UseAuthentication();
app.Use(async (context, next) => {
await next();
var bearerAuth = context.Request.Headers["Authorization"]
.FirstOrDefault()?.StartsWith("Bearer ") ?? false;
if (context.Response.StatusCode == 401
&& !context.User.Identity.IsAuthenticated
&& !bearerAuth) {
await context.ChallengeAsync("oidc");
}
});
Bob's your uncle... and thanks to this post for helping considerably!! oipapio.com/question-1510997

Swagger UI using swasbuckle is getting me CORS Origin issue on login redirect from Azure

I'm facing a new issue with swashbuckle and login redirection after make a request and my token has been expired.
I really don't know if is a common issue or if this thins can't be done, that's why I make the question.
My request to my api enponit redirects fine but, when I click the Execute button on swagger UI, the request to https://localhost:port/api/v1/Contacts, the response is 302 and then try to redirect me to my login provider with this issue and I cannot redirect to login again.
What I'm missing here????
this is my authentication config:
//Add Auth options
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.ClientId = ClientId;
options.Authority = $"{Instance}/{TenantId}";
options.SignedOutRedirectUri = SignedOutRedirectUri;
// Add needed scopes
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("roles");
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
return Task.CompletedTask;
}
};
})
.AddCookie()
.AddAzureAdBearer(options => configuration.Bind("bearer", options));

Dotnet Core authentication CorrelationFailed

I'm trying to setup authentication on a brand new dotnet core app.
I'm using IdentityServer that is working fine as it is being used for other apps.
I get an error that does not say much more than "Correlation Failed".
Looking at the output in VS2017 I see a warning a bit before the exception that say the following:
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler:Warning:
.AspNetCore.Correlation. state property not found.
Here is my config:
public void ConfigureServices(IServiceCollection services)
{
Debugger.Break();
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.SlidingExpiration = true;
options.Cookie.Name = "MyAwesomeCookie";
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
})
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = IdentityServerConstants.IdentityServerUrl;
options.ClientId = "myclientid";
options.ClientSecret = "supersecuresecret";
//options.CorrelationCookie.Name = "something";
foreach (var s in myScopes)
{
options.Scope.Add(s);
}
options.ResponseType = "code id_token token";
options.CallbackPath = new PathString("/");
options.UseTokenLifetime = false;
options.RequireHttpsMetadata = false;
});
}
I modified some values for security reasons and I excluded the event hoocking as I believe that it is irrelevant.
I could track it down to this file:
https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs
If the last method on this file returns false, then you get the "Correlation Failed" error.
However, I spent a long time googling the error and could not find how to fix it. I'm probably missing something trivial in the config...
Any clue?
I don't have an explanation for why this is the case, but I encountered the same error and managed to resolve it by removing this line:
options.CallbackPath = new PathString("/");
I had this issue, which was caused by the following option being set in the RouteOptions:
options.LowercaseQueryStrings = true;
This caused the state query parameter to be lower cased when performing the redirects back to the application. Since state is case sensitive, it caused the state property not found error.