asp.net core - stuck in authentication loop - authentication

if I configure authentication with
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
all works fine, but if I configure it
.AddAuthentication(options => {
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
Even after a succesful login, the user gets redirected to the logon page.
I would expect the same behavior for both configs. Any thoughts ?

Related

IdentityServer4 - LogoutRequest.PostLogoutRedirectUri is null

I have an identity server project that I am working on that for some reason is setting the log out url as null. Using the "BuildLogoutViewModelAsync(logoutId)" function found in the QuickStart, this line is returning null, but in the log the "post_logout_redirect_uri" is set and is set correctly.
var context = await this.interactionService.GetLogoutContextAsync(logoutId);
Here is the necessary information from the log file.
2018-06-11T16:11:27.1301566-04:00 0HLEFQTLST4A2:00000006 [INF] Profile service returned to the following claim types: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress name given_name email UserId PayeeId ErpPayeeId MyReports PipelineCRM Start Start IdentityManager SpecialOrder MarketingEmail PunchOut CustomerEntityManager AttributeManager SpecialOrdersLite PayeeManagement VirtualVideoTraining MySurveys NAMToolkit Dashboard FrameworkManager FrameworkManager ContractManagement SalesDashboard HRSInstallationLeadForm PunchoutManagement CompetitiveIntelligence SpecialOrderRequest LuceneIndexSearch CompetitiveIntelligence PKB CompetitiveIntelligence CompetitiveIntelligence CompetitiveIntelligence PKB SpecialOrderRequestPOC AppsManagement Genie Testing1234 October Deviation ReportDeliveryManagement ReportDeliveryManagement RgTest TestingUpdates1 TrainingDemo ABCDE CustomerSegmentationManager CustomerSegmentationManager TestingTemplate2 AppsMgmt AppsMgmt AppsMgmt InventoryControlWorkflow ProPurchaseCardMaint" (a9217bec)
2018-06-11T16:11:27.1354674-04:00 0HLEFQTLST4A2:00000006 [INF] Request finished in 1263.2834ms 200 application/json; charset=UTF-8 (791a596a)
2018-06-11T16:11:29.9711755-04:00 0HLEFQTLST4A1:00000007 [INF] Request starting HTTP/1.1 GET http://localhost:44329/connect/endsession?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A21402%2Fsignout-callback-oidc&state=CfDJ8A54aiN-IdtIpcL6PAgpJbMSpzMkkd27BJqnGFbTgRwiqdf1XkpfMApJnfC0_3BOsVALgr2skPwBmy74ToICvY6ZjWsd4BJLHkVqJD9Cp45zXBKH37iX2o2y6A8wD30yghQDcA4B2iPHg6eAjliWN4h8jv3PdlE_gjIKiNY-Eckk&x-client-SKU=ID_NET&x-client-ver=2.1.4.0 (ca22a1cb)
2018-06-11T16:11:29.9738375-04:00 0HLEFQTLST4A1:00000007 [INF] AuthenticationScheme: "idsrv" was successfully authenticated. (1805f3b3)
2018-06-11T16:11:29.9759119-04:00 0HLEFQTLST4A1:00000007 [INF] AuthenticationScheme: "idsrv" was successfully authenticated. (1805f3b3)
2018-06-11T16:11:29.9796082-04:00 0HLEFQTLST4A1:00000007 [INF] Invoking IdentityServer endpoint: "IdentityServer4.Endpoints.EndSessionEndpoint" for "/connect/endsession" (f7642de5)
2018-06-11T16:11:29.9963239-04:00 0HLEFQTLST4A1:00000007 [INF] End session request validation success
"{
\"SubjectId\": \"MOORESTOWN\\rpannell1\",
\"Raw\": {
\"post_logout_redirect_uri\": \"http://localhost:21402/signout-callback-oidc\",
\"state\": \"CfDJ8A54aiN-IdtIpcL6PAgpJbMSpzMkkd27BJqnGFbTgRwiqdf1XkpfMApJnfC0_3BOsVALgr2skPwBmy74ToICvY6ZjWsd4BJLHkVqJD9Cp45zXBKH37iX2o2y6A8wD30yghQDcA4B2iPHg6eAjliWN4h8jv3PdlE_gjIKiNY-Eckk\",
\"x-client-SKU\": \"ID_NET\",
\"x-client-ver\": \"2.1.4.0\"
}
}" (8a893fca)
2018-06-11T16:11:30.0114218-04:00 0HLEFQTLST4A1:00000007 [INF] Request finished in 40.0686ms 302 (791a596a)
2018-06-11T16:11:30.0233725-04:00 0HLEFQTLST4A2:00000007 [INF] Request starting HTTP/1.1 GET http://localhost:44329/account/logout?logoutId=CfDJ8Lr1ecTh1x5IjvA0NxR18eixqgY1PROntfeC5wQJbnQmhM8qTPkm3Dt4ckYZ5sm1NFGrcOh2t67DG6X5buzj8klwDUz8rXzYBIFoTpxIKk4Zi-BhQIimvGKeukPMtgodz16q47X8PTqvaq0TIPLNPvl-QEh54ZZBafc9lk0amvlttW4CPfGGwoCpUJV_vwt9n6B7uu4_WEKaX65qF8O0vu7f-i-IZ_up2T19USJoZMSmy5uRo7-ZpReWgMfB6Ym2jOrWYA2KQBlKgczfmAWyj7eGbz0jRXecCbgcqwIVfVHsgWGL4-DdvM44YG7mp7-AvJAQ1ZOeLT2ootHcwt_ulYNb_zsy-OCT-XdblPBGAXuLqzuTuvEpbkUnF0cE2Amltwmq_ZWc89GK9QG_Ectubzl23k3S0oJ0AjHFSXCezPm61nRjiKhLtY6O1soIgLzSv_NyjdwQIbmJByiBv1NsH7I (ca22a1cb)
2018-06-11T16:11:30.0259399-04:00 0HLEFQTLST4A2:00000007 [INF] AuthenticationScheme: "idsrv" was successfully authenticated. (1805f3b3)
2018-06-11T16:11:30.0280733-04:00 0HLEFQTLST4A2:00000007 [INF] AuthenticationScheme: "idsrv" was successfully authenticated. (1805f3b3)
2018-06-11T16:11:30.0356306-04:00 0HLEFQTLST4A2:00000007 [INF] Executing action method "IBI.Login.Service.Controllers.AccountController.Logout (IBI.Login.Service)" with arguments (["CfDJ8Lr1ecTh1x5IjvA0NxR18eixqgY1PROntfeC5wQJbnQmhM8qTPkm3Dt4ckYZ5sm1NFGrcOh2t67DG6X5buzj8klwDUz8rXzYBIFoTpxIKk4Zi-BhQIimvGKeukPMtgodz16q47X8PTqvaq0TIPLNPvl-QEh54ZZBafc9lk0amvlttW4CPfGGwoCpUJV_vwt9n6B7uu4_WEKaX65qF8O0vu7f-i-IZ_up2T19USJoZMSmy5uRo7-ZpReWgMfB6Ym2jOrWYA2KQBlKgczfmAWyj7eGbz0jRXecCbgcqwIVfVHsgWGL4-DdvM44YG7mp7-AvJAQ1ZOeLT2ootHcwt_ulYNb_zsy-OCT-XdblPBGAXuLqzuTuvEpbkUnF0cE2Amltwmq_ZWc89GK9QG_Ectubzl23k3S0oJ0AjHFSXCezPm61nRjiKhLtY6O1soIgLzSv_NyjdwQIbmJByiBv1NsH7I"]) - ModelState is Valid (ba7f4ac2)
I am using the MVCHybrid from the samples solution with this setup in the client.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.Cookie.Name = "mvchybrid";
})
.AddOpenIdConnect("oidc", options =>
{
// options.SignInScheme = "mvchybrid";
options.Authority = "https://localhost:44329/";
options.RequireHttpsMetadata = false;
options.ClientSecret = "superSecretPassword";
options.ClientId = "webFrameworkOpenIdClient";
options.Resource = "openid profile api1 offline_access";
options.ResponseType = "code id_token token";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
// options.Scope.Add("email");
options.Scope.Add("api1");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SignedOutRedirectUri = "http://localhost:21402/";
// options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
});
The MvcHybrid Logout action result looks like this
public IActionResult Logout()
{
return new SignOutResult(new[] { "Cookies", "oidc" });
}
The url is in the database and is setup as both http://localhost:21402/ and http://localhost:21402/signout-callback-oidc to be sure that both are available.
Can anybody give me some thoughts how to get this setup correctly? From everything I am seeing the data is setup correctly and the data is going over the wire correctly.
Thoughts?
Found it, the configuration was wrong. The SaveTokens = true which will send the id_token via the query string.
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:44329/";
options.RequireHttpsMetadata = false;
options.ClientSecret = "superSecretPassword";
options.ClientId = "webFrameworkOpenIdClient";
options.Resource = "openid profile api1 offline_access";
options.ResponseType = "code id_token token";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("api1");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SignedOutRedirectUri = "http://localhost:21402/";
/* HERE */
options.SaveTokens = true;
/* HERE */
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
});
I had a previous issue where the query string was too large because the number of claims my user will have. Going over to the web.config in the identity server solution I updated to the max query string setting to an extremely large number. The default is 2048 and my token was over 3000 characters. This allow the id_token_hint parameter to be sent which is necessary for a post_logout_redirect_uri to be picked up. See the new log entry below.
<security>
<requestFiltering>
<requestLimits maxQueryString="32768" />
</requestFiltering>
</security>
</system.webServer>
2018-06-12T11:54:48.6190847-04:00 0HLEGFIG5NHS6:0000000A [INF] End session request validation success
"{
\"ClientId\": \"webFrameworkOpenIdClient\",
\"ClientName\": \"Client For IBI Web Applicaiton Framework\",
\"SubjectId\": \"MOORESTOWN\\rpannell1\",
\"PostLogOutUri\": \"http://localhost:21402/signout-callback-oidc\",
\"State\": \"CfDJ8A54aiN-IdtIpcL6PAgpJbOn0XeMop9RKInYCbgTcAxYu2fkXOF7qQrgD8XAilOa4LsQSm7kC40OxybIKAnhMWS3EY_4bHadBJ8yMwUFhNbAB5p6AAggJi_Jvm7ewcoRG_gi0xshxzZ9df4aAemnJpDl0KePcJIq1E-SCH9LTVkh\",
\"Raw\": {
\"post_logout_redirect_uri\": \"http://localhost:21402/signout-callback-oidc\",
\"id_token_hint\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4MDI3ZjIyMDM1NmQzNTIyNDkzNWU4ZDIxY2RhMGVkIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1Mjg4MTg4ODMsImV4cCI6MTUyODgxOTE4MywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzMjkiLCJhdWQiOiJ3ZWJGcmFtZXdvcmtPcGVuSWRDbGllbnQiLCJub25jZSI6IjYzNjY0NDE1NjY1OTMxNTQ2MS5NR1ZqWkdZMk56QXRPV016WlMwME5UYzRMV0kzWldJdE5qRXhZVGRrTmpNNFltSXpPRGhqWmpFeFpXSXROemRtWWkwMFpXWTJMVGd5TW1VdFl6VTVZVFUyTWpjMVpHUTQiLCJpYXQiOjE1Mjg4MTg4ODIsImF0X2hhc2giOiJNX0FCWHg1VlhxOEZOLXJMWTd0UHdBIiwic2lkIjoiNzc1ZmViMTY3MjMxZjU5MjNkNDEyNzFhNDM1YWU5ZjIiLCJzdWIiOiJNT09SRVNUT1dOXFxycGFubmVsbDEiLCJhdXRoX3RpbWUiOjE1Mjg4MTg4NzgsImlkcCI6Im9pZGMiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiTU9PUkVTVE9XTlxccnBhbm5lbGwxIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvZ2l2ZW5uYW1lIjoiUm9kbmV5IFBhbm5lbGwiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJSb2RuZXkuUGFubmVsbEBpbnRlcmxpbmVicmFuZHMuY29tIiwibmFtZSI6Ik1PT1JFU1RPV05cXHJwYW5uZWxsMSIsImdpdmVuX25hbWUiOiJSb2RuZXkgUGFubmVsbCIsImVtYWlsIjoiUm9kbmV5LlBhbm5lbGxAaW50ZXJsaW5lYnJhbmRzLmNvbSIsIlVzZXJJZCI6OTY1OCwiUGF5ZWVJZCI6OTE5MiwiRXJwUGF5ZWVJZCI6MTEyMDcsIk15UmVwb3J0cyI6IkJhc2ljIiwiUGlwZWxpbmVDUk0iOiJCYXNpYyIsIlN0YXJ0IjpbIkJhc2ljIiwiTWFuYWdlciJdLCJJZGVudGl0eU1hbmFnZXIiOiJJbXBlcnNvbmF0b3IiLCJTcGVjaWFsT3JkZXIiOiJTT01hbmFnZXIiLCJNYXJrZXRpbmdFbWFpbCI6IkJhc2ljIiwiUHVuY2hPdXQiOiJBZG1pbiIsIkN1c3RvbWVyRW50aXR5TWFuYWdlciI6IkluaXRpYWxDdXN0b21lckVudGl0eU1hbmFnZXJSb2xlIiwiQXR0cmlidXRlTWFuYWdlciI6IkJhc2ljIiwiU3BlY2lhbE9yZGVyc0xpdGUiOiJTcGVjaWFsT3JkZXJzTGl0ZVJvbGUiLCJQYXllZU1hbmFnZW1lbnQiOiJQYXllZU1hbmFnZW1lbnQiLCJWaXJ0dWFsVmlkZW9UcmFpbmluZyI6IkFkbWluIiwiTXlTdXJ2ZXlzIjoiU3VydmV5TWFuYWdlciIsIk5BTVRvb2xraXQiOiJOQU1Ub29sa2l0IiwiRGFzaGJvYXJkIjoiRGFzaGJvYXJkIiwiRnJhbWV3b3JrTWFuYWdlciI6WyJEZXZlbG9wZXIiLCJBZG1pbmlzdHJhdG9yIl0sIkNvbnRyYWN0TWFuYWdlbWVudCI6IkxlZ2FsIEFkbWluIiwiU2FsZXNEYXNoYm9hcmQiOiJTYWxlc0Rhc2hib2FyZCIsIkhSU0luc3RhbGxhdGlvbkxlYWRGb3JtIjoiU2FsZXMgUmVwIiwiUHVuY2hvdXRNYW5hZ2VtZW50IjoiUHVuY2hvdXRNYW5hZ2VtZW50IiwiQ29tcGV0aXRpdmVJbnRlbGxpZ2VuY2UiOlsiQ0lBIiwiU2FsZXMgUmVwIiwiUHJvZHVjdCBEYXRhIE93bmVyIiwiUERTIiwiUERTKyJdLCJTcGVjaWFsT3JkZXJSZXF1ZXN0IjoiU2FsZXMgU3VwcG9ydCIsIkx1Y2VuZUluZGV4U2VhcmNoIjoiTHVjZW5lSW5kZXhTZWFyY2giLCJQS0IiOlsiUEtCIiwiQWRtaW4iXSwiU3BlY2lhbE9yZGVyUmVxdWVzdFBPQyI6IkFkbWluIiwiQXBwc01hbmFnZW1lbnQiOiJBZG1pbiIsIkdlbmllIjoiR2VuaWUiLCJUZXN0aW5nMTIzNCI6IlRlc3RpbmcxMjM0IiwiT2N0b2JlciI6Ik9jdG9iZXIiLCJEZXZpYXRpb24iOiJBZG1pbiIsIlJlcG9ydERlbGl2ZXJ5TWFuYWdlbWVudCI6WyJSZXBvcnREZWxpdmVyeU1hbmFnZW1lbnRSb2xlIiwiVVR3YXJlaG91c2UiXSwiUmdUZXN0IjoiQWRtaW4iLCJUZXN0aW5nVXBkYXRlczEiOiJUZXN0aW5nVXBkYXRlczEiLCJUcmFpbmluZ0RlbW8iOiJUcmFpbmluZ0RlbW8iLCJBQkNERSI6IkFkbWluIiwiQ3VzdG9tZXJTZWdtZW50YXRpb25NYW5hZ2VyIjpbIkFkbWluIiwiQmFzaWMiXSwiVGVzdGluZ1RlbXBsYXRlMiI6IkFkbWluIiwiQXBwc01nbXQiOlsiQWRtaW4iLCJHTyIsIkRldmVsb3BlciJdLCJJbnZlbnRvcnlDb250cm9sV29ya2Zsb3ciOiJBZG1pbiIsIlByb1B1cmNoYXNlQ2FyZE1haW50IjoiYWRtaW4iLCJhbXIiOlsiZXh0ZXJuYWwiXX0.TiTurTiN5g__lnOuFBWNi5puHyEv9yfHxugi5USuMV77FErl05aKv9qEKi72vwyG1ELWWtwR0SC73BEEvLXyHx7A-3RO-bvCLbvQhaQs4iTJQ642Iir0cTkqfZVje_DavQbqWnREWHsmwikUsyu-YLf82CqTpSm1OV0hbauPW02wNZGxJU1jRYhPr9dYraNUo8KL77M3mWx6CnZ5XFf6igHnUTSbH5xv0phnPbsDXJ5bsvuzo4DY-IQLyM3K17gAfKy8DPFoiVzZxKM1_fGXbEr9Hf5qw7GCd7I5sGxwfBz5RKYcqa9ahcTt9WBq6QrslFXrl76N4MgY40v3LeHW4w\",
\"state\": \"CfDJ8A54aiN-IdtIpcL6PAgpJbOn0XeMop9RKInYCbgTcAxYu2fkXOF7qQrgD8XAilOa4LsQSm7kC40OxybIKAnhMWS3EY_4bHadBJ8yMwUFhNbAB5p6AAggJi_Jvm7ewcoRG_gi0xshxzZ9df4aAemnJpDl0KePcJIq1E-SCH9LTVkh\",
\"x-client-SKU\": \"ID_NET\",
\"x-client-ver\": \"2.1.4.0\"
}
}" (8a893fca)

User.Identity.IsAuthenticated Being Checked Twice?

I'm a bit new to .net core and I'm struggling with how to approach this, so forgive me if my current approach is wrong or inherently frowned upon.
However, I have set up my application to use Jwt and [Authorization] when working with any API Endpoints. Since this is not a single page application, I need to authorize access to specific views within the application as well. For example, I only want users to be given access to the Dashboard of the application if they have been signed in.
My sign in method (from a pseudo description) checks credentials and generates a JWT token to return to the client side for storage if everything checks out. Before returning the token to the client I am also calling the following:
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, User);
Which works for the most part. It creates a cookie and stores information in the cookie, but when it gets to the controller where I want to validate if the user is authenticated, the validation runs twice. The first time it runs, the isAuthenticated is false. The second time it runs, it is true. I'm having trouble finding out why it's running twice (maybe I've missed something here?), and beginning to wonder if I'm approaching this correctly or not.
public IActionResult Dashboard()
{
bool isAuthenticated = User.Identity.IsAuthenticated;
return isAuthenticated ? View() : (IActionResult)Forbid();
}
My Jwt Authorization works just fine, but my authorization on cookies is not working at all. For example, the Authorize in the below method is working flawlessly.
[Authorize]
public OkObjectResult Test()
{
return Ok(HttpStatusCode.OK);
}
Here is my Startup.cs ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‚ÄĆ‚Äč)
.RequireAuthenticatedUser().Build());
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Home/Index";
options.AccessDeniedPath = "/Home/Index";
options.Cookie.Name = CookieAuthenticationDefaults.AuthenticationScheme;
options.ExpireTimeSpan = new TimeSpan(365, 0, 0, 0);
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = SameSiteMode.None;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
//ValidAudience = "the audience you want to validate",
ValidateIssuer = false,
//ValidIssuer = "the issuer you want to validate",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("XXX")),
ValidateLifetime = true, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
};
});
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.Secure = CookieSecurePolicy.SameAsRequest;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Configure the DB Access.
services.AddDbContext<DbAccess>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// Ensure that we are adding the repos to our scope.
services.AddScoped<IResetRepository, ResetRepository>();
services.AddScoped<IAuthRepository, AuthRepository>();
// Inject our email settings.
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
// Let's set up the email sender as transient.
services.AddTransient<IEmailSender, EmailSender>();
}
And here is my Configure method within Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
var cookiePolicyOptions = new CookiePolicyOptions
{
Secure = CookieSecurePolicy.SameAsRequest,
MinimumSameSitePolicy = SameSiteMode.None
};
app.UseCookiePolicy(cookiePolicyOptions);
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
I'm hoping I've not missed anything super obvious here. Any assistance is greatly appreciated.
Here are some images (added after the initial post). Hopefully these can help resolve the problem?

Change Header name in nginx for basic auth or Aspnetcore for JWT

I am having a difficult issue. my problem is I am using asp.net core 2.0 website with jwt . When JWT passes token it has a header name as Authentication now I need to add HTTP Basic Auth of Nginx which also uses the same header. after login Nginx shows 403 as it overrides the header.
I need to change either JWT header or Nginx header whichever is possible.
My issue has been resolved
You need to set override the OnMessageReceived event:
https://github.com/aspnet/AspNetCore/issues/3728
I solve with this in Startup.ConfigureServices
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
var header = context.HttpContext.Request.Headers["custom-header"].ToString();
if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
context.Token = header.Substring("Bearer ".Length).Trim();
}
return Task.CompletedTask;
}
};
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
And in Startup.Configure
app.UseAuthentication();

Implementing private_key_jwt and client_secret_jwt with Identity Server 4; providing client credentials using a JWT token

Using Identity Server 4, how do you hook into the exchange between the client and server when using the authorization_code flow to provide Client credentials to the Identity Server using a JWT Token?
Below is the solution:
In ConfigureServices the is key to hook into the Identity Server pipeline and provide a call-back for the event OnAuthorizationCodeReceived. This event is called at the point in the pipeline where the authorization code is received back from Identity server during the normal exchange between the client and server as described by https://www.ietf.org/rfc/rfc6750.txt.
Doing this give you the opportunity to create the JWT token and make it available from that point on in the pipeline.
Configuration on the client
services.AddAuthentication(options =>
...
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.RemoteAuthenticationTimeout = TimeSpan.FromMinutes(10);
options.UseTokenLifetime = false;
options.RequireHttpsMetadata = false;
options.Authority = "http://localhost:44320/";
options.ClientId = "cliend-id";
options.ClientSecret = "client-secret";
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events.OnAuthorizationCodeReceived = delegate (AuthorizationCodeReceivedContext context)
{
var clientassertion = new ClientAssertion("client-id", "http://localhost:44320/connect/token");
var assertion = clientassertion.CreateJwtClientAssertionAsymmetric("localhost");
context.TokenEndpointRequest.ClientAssertion = assertion.ClientAssertion;
context.TokenEndpointRequest.ClientAssertionType = assertion.ClientAssertionType;
return Task.CompletedTask;
};
...
Configuration on the server
As http://docs.identityserver.io/en/release/topics/secrets.html?highlight=beyond indicates under the section beyond shared secrets.
The important bit here is to ensure the type and value are aligned as in the example below.
var client = new Client
{
...
ClientSecrets =
{
new Secret
{
Type = IdentityServerConstants.SecretTypes.X509CertificateBase64,
Value = "MIIDATCC..."
}
},
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowedScopes = { "api1", "api2" }
};
Implementation
Implement the interfaces ISecretParser and ISecretValidator
Then add to implementations to the DI system in ConfigureServices.
Eg.
builder.AddSecretParser()
builder.AddSecretValidator()
If your Validator is not getting called, ensure RequireClientSecret is set to true.
Take the appropriate action in the parser, and validator (from the parse return success for failure).
This approach work for both private_key_jwt and client_secret_jwt.

ASP.Net Core - no redirect on API auth error

In my ASP.NET Core project I got a few API-Controllers with jwt-authorization like this:
[Route("api/v1/[controller]")]
public class MyController : Controller
{
[HttpGet("[action]")]
[Authorize(Policy = MyPolicy)]
public JsonResult FetchAll()
{
}
}
When authorization for accessing the action FetchAll() Fails I want HttpStatusCode.Forbidden as response. Instead Mvc does a reroute to Account/Login?ReturnUrl=[...]
I tried to capture the Redirect-Events and return Forbidden/Unauthorized overriding the Cookie Events to no avail:
app.UseIdentity();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = TokenController.DummyKey,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(0)
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters,
});
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AutomaticAuthenticate = false,
AutomaticChallenge = false,
AuthenticationScheme = "BsCookie",
CookieName = "access_token",
TicketDataFormat = new CustomJwtDataFormat(SecurityAlgorithms.HmacSha256, tokenValidationParameters),
Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = context =>
{
if (context.Request.Path.StartsWithSegments("/api") && context.Response.StatusCode == (int)HttpStatusCode.OK)
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
else
context.Response.Redirect(context.RedirectUri);
return Task.FromResult(0);
},
OnRedirectToAccessDenied = context =>
{
if (context.Request.Path.StartsWithSegments("/api") && context.Response.StatusCode == (int)HttpStatusCode.OK)
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
else
context.Response.Redirect(context.RedirectUri);
return Task.FromResult(0);
}
},
});
Both Events are never called and the Visual Studio output Shows that fetchall Fails and Account/Login will be returned instead:
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:6460/api/v1/Lehrer/GetAll application/json
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: Successfully validated the token.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: HttpContext.User merged via AutomaticAuthentication from authenticationScheme: Bearer.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed for user: (null).
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 ().
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: AuthenticationScheme: Bearer was forbidden.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware:Information: AuthenticationScheme: Identity.Application was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Sam.Learning2.Controllers.LehrerController.GetAll (Sam.Learning2) in 49.7114ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 121.6106ms 302
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:6460/Account/Login?ReturnUrl=%2Fapi%2Fv1%2FLehrer%2FGetAll
I want my APIs to return 401/403 instead of redirecting to Login - how do I achieve this when above code does not work?
Update ASP.NET Core 2.x
The authorization changed a little in ASP.NET Core 2.0. The answer below ist just valid for ASP.NET Core 1.x. For ASP.NET Core 2.0 refer to this answer and this GitHub annoucement.
ASP.NET Core 1.x
What you seems to have forgotten is that app.UseIdentity() also registers the cookie middleware.
var options = app.ApplicationServices.GetRequiredService<IOptions<IdentityOptions>>().Value;
app.UseCookieAuthentication(options.Cookies.ExternalCookie);
app.UseCookieAuthentication(options.Cookies.TwoFactorRememberMeCookie);
app.UseCookieAuthentication(options.Cookies.TwoFactorUserIdCookie);
app.UseCookieAuthentication(options.Cookies.ApplicationCookie);
and the ASP.NET Core Identity sets the AutomaticChallange to true for cookie (ApplicationCookie) middleware (see source). Hence the redirect to /Account/Login?ReturnUrl. You will need do disable this option in Identity.
services.AddIdentity(options =>
{
options.Cookies.ApplicationCookie.AutomaticChallenge = false;
});
If you really want have Identity's Auth (login to web page) and JWT, you'd need to register the middlewares based on the url. So i.e. app.UseIdentity() is only registered for non-api urls and Jwt middleware is only registered for urls starting with /api.
You can do that with .MapWhen (docs).
app.MapWhen(context => !context.Request.Path.StartsWith("/api"), branch =>
{
branch.UseIdentity();
});
Now branch.UseIdentity() will only be used, for URLs which don't start with /api, which usually are your MVC views where the redirect to /Account/Login is desired.
I just use Barry Dorrans Asp Net Authorization Workshop
in ConfigureServices I just add services.AddAuthorization();.
and in Configure add this code:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookie",
LoginPath = new PathString("/Account/Login/"),
AccessDeniedPath = new PathString("/Account/Forbidden/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 401;
}
else
ctx.Response.Redirect(ctx.RedirectUri);
return Task.CompletedTask;
},
OnRedirectToAccessDenied = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 403;
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
}
return Task.CompletedTask;
}
}
}
In Mvc reroute to Account/Login?ReturnUrl=[...] and in API you will get 401 or 403.
Microsoft's web api stack is set up to do this out-of-the-box. The solution is at the client end.
Add this header to the client request:
'X-Requested-With': 'XMLHttpRequest'
Web api looks for that header. When present it returns a 401 if the request is unauthenticated. When the header is absent it returns the redirect to the login page.
See this https://github.com/aspnet/Security/issues/1394#issuecomment-326445124
I think you only need the more complex code in the cookie events if you cannot modify the client.

Resources