ASP.NET Core 6 session get returns null - asp.net-core

I am using ASP.NET Core 6 with razor pages.
I can set the session like the code below but when I stop and run the project, the session get is returning null, but if I set the session again in a post request through a form and button it appears again!
I changed the options in program.cs but nothing seems to be working.
I am confused about this problem!
I have an example project in .NET 3 and it keeps the session but mine is not working
This is how I set the session:
HttpContext.Session.SetInt32(Constants.ShoppingCart, count);
And I get it like this in my Layout.cs:
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
#if (HttpContextAccessor.HttpContext.Session.GetInt32(Constants.ShoppingCart) != null)
{
<a href="~/cart/Index" class="btn btn-primary navbar-btn">
#{
var count = HttpContextAccessor.HttpContext.Session.GetInt32(Constants.ShoppingCart);
}
<i class="fa fa-shopping-cart"></i>
<span>#count items in cart</span>
</a>
}
My program.cs file:
using AspDotNetApps.Core.Middleware;
using AutoMapper;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost
.CaptureStartupErrors(true)
.UseSetting("detailedErrors", "true");
builder.Services.AddControllersWithViews()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
var keysDirectoryName = "mydir";
var keysDirectoryPath = Path.Combine(builder.Environment.ContentRootPath, keysDirectoryName);
if (!Directory.Exists(keysDirectoryPath))
{
Directory.CreateDirectory(keysDirectoryPath);
}
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keysDirectoryPath))
.SetApplicationName("store")
.SetDefaultKeyLifetime(TimeSpan.FromDays(30));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(Constants.AdminRole, policy => policy.RequireRole(Constants.AdminRole));
});
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.Cookie.Name = ".JewelryStore.Session";
options.IdleTimeout = TimeSpan.FromHours(24);
options.Cookie.IsEssential = true;
options.Cookie.HttpOnly = true;
});
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
builder.Services.ConfigureApplicationCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(2);
options.SlidingExpiration = true;
options.LoginPath = $"/Admin/Login";
options.LogoutPath = $"/Admin/Logout";
options.AccessDeniedPath = $"/Areas/Admin/AccessDenied";
});
builder.Services.AddRazorPages();
builder.Services.AddCors(options => options.AddPolicy("Cors", builder =>
{
builder
.AllowAnyHeader()
.AllowAnyMethod()
.WithHeaders("accept", "content-type", "origin")
.AllowAnyOrigin();
}));
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
Mapper.Initialize(x =>
{
x.AddProfile<MappingProfile>();
});
builder.Services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Admin", "Main");
});
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error/500");
app.UseStatusCodePagesWithRedirects("/Error/{0}");
app.UseHsts();
}
app.UseCors("Cors");
app.UseSecurityHeadersMiddleware(
new SecurityHeadersBuilder()
.AddDefaultSecurePolicy());
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
// redirects the admin when using /admin route to the admin area
endpoints.MapAreaControllerRoute(
"admin",
"admin",
"Admin/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
"default", "{controller=Home}/{action=Index}/{id?}");
});
app.Run();

I can set the session like the code below but when I stop and run the
project, the session get is returning null, but if I set the session
again in a post request through a form and button it appears again!I
am confused about this problem! I have an example project in .NET 3
and it keeps the session but mine is not working
Well, I think you are certainly misunderstanding between session and cookie concept. Session of course has nothing to deal with browsers and while application stops or shutdown session must not retain at all. So the project in .NET 3 you are referring definitely something dealing with cookies.
In addition, If you want to keep some value on browser and want to retrieve it even the browser closed and if cookies doesn't expire you would like it to get back in that scenario, you must need to deal with cookie for sure.
So far, I have gone through your requirement, you can only implement using cookie not session.
Razor Page cshtml.cs
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly IHttpContextAccessor _contextAccessor;
public IndexModel(ILogger<IndexModel> logger, IHttpContextAccessor contextAccessor)
{
_logger = logger;
_contextAccessor = contextAccessor;
}
public void OnGet()
{
var count = HttpContext.Request.Cookies["_count"];
if (count == null)
{
CookieOptions options = new CookieOptions();
options.Expires = DateTime.Now.AddHours(1);
_contextAccessor.HttpContext?.Response.Cookies.Append("_count", "600", options);
}
}
}
Razor Page cshtml
#page
#model IndexModel
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
#{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about building Web apps with ASP.NET Core.</p>
</div>
<h1>Current Count: #HttpContextAccessor.HttpContext.Request.Cookies["_count"]</h1>
program.cs
builder.Services.AddRazorPages();
builder.Services.AddHttpContextAccessor();
Output:
Note: You should see above infomration while you navigate to your browser application console.
Note: Your implementation and the behavior are correct in regards of session as it wouldn't keep anyting once the server or app restarted. However, if you would like to keep some value and want to retrieve it from browser side, you must need to use cookie. If you will need more information, please have a look on our official document here

Related

ASP.NET Core: OpenIdConnect: message.State is null or empty

I get this error:
OpenIdConnectAuthenticationHandler: message.State is null or empty.
with the URL https://localhost:7208/home/index, but the authentication works with the url https://localhost:7208/.
Can anyone help me understand this?
enter image description here
enter image description here
This is my code:
Program.cs:
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using System.Security.Claims;
using System.Web.Mvc;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddMvc().AddSessionStateTempDataProvider();
builder.Services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
});
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
})
.AddOpenIdConnect(options =>
{
options.Authority = builder.Configuration["OpenIdConfigurations:Authority"];
options.MetadataAddress = builder.Configuration["OpenIdConfigurations:MetadataAddress"];
options.ResponseType = builder.Configuration["OpenIdConfigurations:ResponseType"];
options.GetClaimsFromUserInfoEndpoint = Convert.ToBoolean(builder.Configuration["OpenIdConfigurations:GetClaimsFromUserInfoEndpoint"]);
options.RequireHttpsMetadata = Convert.ToBoolean(builder.Configuration["OpenIdConfigurations:RequireHttpsMetadata"]);
options.ClientId = builder.Configuration["OpenIdConfigurations:ClientId"];
options.ClientSecret = builder.Configuration["OpenIdConfigurations:ClientSecret"];
options.CallbackPath = builder.Configuration["OpenIdConfigurations:CallbackPath"];
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"
);
app.Run();
Controller:
namespace OIDCMVC.Controllers
{
[Authorize]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
Note: We use a private provide and expects the call back path.
Callbackpath = "/home/index"
In my case I am using blazor (.net 6) and trying to protect hangfire with Microsoft oauth. To get the auth screen of microsoft login when locating to /hangfire. The solution to this error was as simple as removing CallbackPath from my settings:
// settings found under Azure AD -> App Registration -> Your registration
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "domain", // found under Branding and properties -> publisher domain like : ...outlook.onmicrosoft.com
"TenantId": "tenantid", // found under Overview Directory (tenant) ID GUID
"ClientId": "client_id" // found under Overview Application (client) ID GUID
//"CallbackPath": "/hangfire" REMOVED To get rid off message.State is null error
}
The setup from Program.cs:
services.AddAuthentication().AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd"));
services.AddAuthorization(options =>
{
options.AddPolicy("Hangfire", builder =>
{
builder
.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)
.RequireAuthenticatedUser();
});
});
services.AddHangfire(x =>
{
x.UseSqlServerStorage(connectionString);
x.UseConsole();
});
services.AddHangfireServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHangfireDashboard("/hangfire", new DashboardOptions()
{
Authorization = new List<IDashboardAuthorizationFilter> { new HangfireAuthorizeFilter() },
}).RequireAuthorization("Hangfire");
});
app.UseHangfireDashboard();
Authorization filter:
public class HangfireAuthorizeFilter:
IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var userIdentity = context.GetHttpContext().User.Identity;
return userIdentity is { IsAuthenticated: true };
}
}
And the app registration from Azure AD:
Under your app registration click: Authentication -> Mark "ID tokens" and enter your redirect urls, like: https://localhost:52908/hangfire
I've been integrating multiple custom policies (different flows) in a single app. Removing CallbackPath didn't work as request were coming there from multiple sources, not just the default sign in policy. We were receiving "message.State is null or empty" page, but after navigating to the base path of the app, the user was authenticated and properly logged in.
What ultimately helped was setting SkipUnrecognizedRequests property to true:
.AddOpenIdConnect(options =>
{
...
options.SkipUnrecognizedRequests = true;
});
or using appsettings:
{
...
"AzureAdB2C": {
"Instance": "",
"Domain": "",
"ClientId": "",
...
"SkipUnrecognizedRequests": true
},
...
}
According to the OpenIdConnectOptions.SkipUnrecognizedRequests documentation:
Indicates if requests to the CallbackPath may also be for other components. If enabled the handler will pass requests through that do not contain OpenIdConnect authentication responses. Disabling this and setting the CallbackPath to a dedicated endpoint may provide better error handling. This is disabled by default.
Also, maybe related issue here: OIDC handler running on '/' crashes even with SkipUnrecognizedRequests set to true #10028

Visual studio's ASP.NET core with angular template always returns 401

I'm using Microsoft Visual Studio Community 2019 Version 16.10.2. I created an ASP.net core 5 project from their template "ASP.net core with Angular" with Authentication checked. But Every time I request an API Method marked with the attribute [Authorize] I get 401.
Their template was supposed to work with no problem but I got some trouble logging in. I fixed them but, the only problem I can't figure out how to fix is the 401 code returned ASP.
I read the doc several times but I could not find any useful information.
The thing is: I can create accounts and login with no problem. When I login, the server returns the token. From the Angular app it shows the name of the logged-in user. But when to access an [Authorize] controller it returns 404.
Here is the link of the project I pushed to github for better debugging.
Here is the startup code:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(op =>
{
// I added these lines because I was getting the error "Error: Client Angular_identity_test is not allowed access to scope Angular."
op.Clients[0].AllowedScopes = new List<string> { "Angular", "identity", "testAPI" };
op.ApiScopes = new Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ApiScopeCollection(new List<ApiScope> { new ApiScope("Angular"), new ApiScope("identity"), new ApiScope("testAPI") });
op.ApiResources.AddApiResource("Angular", conf => conf.WithScopes(new string[] { "Angular", "identity", "testAPI" }));
op.ApiResources.AddApiResource("identity", conf => conf.WithScopes(new string[] { "Angular", "identity", "testAPI" }));
op.ApiResources.AddApiResource("testAPI", conf => conf.WithScopes(new string[] { "Angular", "identity", "testAPI" }));
});
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
// In public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
// I added this line because some people say that fixed their problems.
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
In the appsettings.json there is:
"IdentityServer": {
"Clients": {
"Angular_identity_test": {
"Profile": "IdentityServerSPA"
}
}
}
And the controller that keeps return 401:
[Authorize()]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
So you say, But when to access a [Authorize] controller it returns 404. use this below code, instead of [Authorize].
[Authorize(AuthenticationSchemes = "Bearer")]
It will resolve your issue.
UPDATE
Use this below code and i assume that your Token is place of appsettings.development.json file. like "TokenKey":"super secret key",
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters=new TokenValidationParameters
{
ValidateIssuerSigningKey=true,
IssuerSigningKey=new SymmetricSecurityKey( Encoding.UTF8.GetBytes(config["TokenKey"]) ),
ValidateIssuer=false,
ValidateAudience=false
};
});
Hope it will resolve your issue.
This will be a proxy issue. In your CLIENTAPP go to the proxy.conf.js and add your controllers route into the PROXY_CONFIG->context array (like "/weatherforecast" is). Then restart your application. You don't need to change .NET Core middleware or services.
UPDATE
I added this DI BASE_URL into my service constructor and it works fine now.
constructor(private http: HttpClient, #Inject('BASE_URL') private bUrl: string) {
this.baseUrl = bUrl + "api/v1/items"; // my endpoint
}

.netcore web API Post does not work without SSL

I have an MVC core 2.2 application with Controllers, API controllers, and some Views. Some of the Views make use of the API endpoints within the same application. All was ok until I enabled TLS 1.2 in my windows server.
Now all endpoints decorated as GET methods work. But all POSTs have stopped working with chrome reporting this POST 'link' net::ERR_CONNECTION_RESET.
Other browsers catch exception at fail and display my error text with object
$.ajax({
url: "/api/Cart",
method: "post",
data: JSON.stringify(vm),
contentType: "application/json"
}).done(function(result) {
console.log(result);
}).fail(function(ex) {
console.log("Error occured while adding to cart" + ex)
});
I want to be able to POST even without SSL. I have disabled TLS 1.2 and restarted but still the same result.
If I browse the site with https://, POST and GET endpoints all work perfectly but if I browse with HTTP://, only GET endpoints work, all POST endpoints do not work.
I have combed the internet for almost 5 hours for something I thought would be simple.
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("DataConnection");
services.AddDbContext<DataContext>(options => options.UseSqlServer(connectionString));
//Inject Connection String to other Classes
services.AddSingleton(_ => connectionString);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<DataContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
});
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddMediatR(typeof(CreateProductCommand).Assembly, typeof(CreateProductCommandHandler).Assembly);
services.AddAutoMapper(typeof(MappingProfile));
// Add memory cache services
services.AddMemoryCache();
services.AddMvc(o =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
o.Filters.Add(new AuthorizeFilter(policy));
}) .SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_2);
}
// HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var serviceProvider = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider;
DataContextSeed.Initialize(serviceProvider);
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Above is the Startup class and a sample post is here
[HttpPost]
public IActionResult Post([FromBody] SaleItemDTO md)
{
if(md != null)
{
if(md.Quantity <= md.Stock)
{
_sales.SalesPerson = User.Identity.Name;
_sales.SalesType = md.SalesType;
return Ok(_sales.ItemsInDb);
}
}
return BadRequest(new { Message = "Not Valid Content posted" });
}
Help with your thoughts.

Asp.net Core 2 with IdentityServer4 - Redirect to Login after cookie expiration

I have an Asp.net Core 2.2 MVC application that authenticates using an IdentityServer4 server.
It is configured as you can see on the bottom, with really short times for quick testing.
The desired behavior is:
Login (suppose without the "remember me checked")
Do things...
Wait until the session expires
On the next navigation click redirect on the login page for a new interactive sign-in
I supposed I must work on cookies and session server side, but my first doubt is that I have to work more with id_token.
Anyway the current behavior is:
Login without the "remember me checked"
Wait until the session expires
Click on a dummy page and I see that the session is empty (as expected) -> The login is available on the top menu
So I click on login -> No login page showed -> a new session server side is available and in the browser there is a new value of ".AspNetCore.Cookies" but the same for ".AspNetCore.Identity.Application" and "idsrv.session".
If I logout, the cookie client side is correctly removed, so at the next login shows the expected credential form.
What I'm doing wrong?
Is it correct to try to get a new interactive sign-in checking the cookie expiration?
Do I have to follow another way working on the ids (id_token) objects?
CODE
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromSeconds(30);
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = Configuration.GetValue<string>("IdentitySettings:Authority");
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
options.Events.OnTicketReceived = async (context) =>
{
context.Properties.ExpiresUtc = DateTime.UtcNow.AddSeconds(30);
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment() || env.IsStaging())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
EDIT
The logout is done as following
public async void OnPost()
{
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc",
new AuthenticationProperties
{
RedirectUri = "http://localhost:5002"
});
}

asp.net core redirect http traffic to https issue

I have just installed an ssl certificate on my host, and thought I would redirect all http traffic to https. I found that there is a new package for helping with it in .net core.
The problem is that it doesn't work for me and I can't figure out why. When I try to navigate to http://mysite.co.uk to test the redirection it fails with a message saying
The page isn't redirecting properly
Firefox has detected that the server is redirecting the request for this address in a way that will never complete.
This problem can sometimes be caused by disabling or refusing to accept cookies.
Here is my stratup.cs:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Playabout.Data;
using Playabout.Models;
using Playabout.Services;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
using Microsoft.AspNetCore.Localization;
using Microsoft.Net.Http.Headers;
using System.Globalization;
using Sakura.AspNetCore.Mvc;
using Microsoft.AspNetCore.ResponseCompression;
using System.IO.Compression;
using System.Linq;
using Microsoft.AspNetCore.Rewrite;
using System.Net;
namespace Playabout
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
//builder.AddUserSecrets<Startup>();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(
config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<GzipCompressionProviderOptions>
(options => options.Level = CompressionLevel.Optimal);
services.AddResponseCompression(options =>
{
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
{
"text/plain",
"text/css",
"application/javascript",
"text/html",
"application/xml",
"text/xml",
"application/json",
"text/json",
// Custom
"text/javascript",
"image/svg+xml"
});
options.Providers.Add<GzipCompressionProvider>();
});
services.AddMvc();
// Add application services.
services.Configure<SmtpConfig>(optionsSetup =>
{
//get from config.json file
optionsSetup.EmailDisplayName = Configuration["SMTP:DisplayName"];
optionsSetup.SmtpPassworrd = Configuration["SMTP:Password"];
optionsSetup.SmtpUserEmail = Configuration["SMTP:Email"];
optionsSetup.SmtpHost = Configuration["SMTP:Host"];
optionsSetup.SmtpPort = Convert.ToInt32(Configuration["SMTP:Port"]);
});
services.Configure<RecaptchaConfig>(optionsSetup =>
{
//get from config.json file
optionsSetup.RecaptchaPublicKey = Configuration["Recaptcha:PublicKey"];
optionsSetup.RecaptchaPrivateKey = Configuration["Recaptcha:PrivateKey"];
});
// Add default bootstrap-styled pager implementation
services.AddBootstrapPagerGenerator(options =>
{
// Use default pager options.
options.ConfigureDefault();
});
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddSession();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public async void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ApplicationDbContext context)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var supportedCultures = new[]
{
new CultureInfo("en-GB"),
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-GB"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
app.UseRewriter(new RewriteOptions()
.AddRedirectToHttps());
app.UseResponseCompression();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
const int durationInSeconds = 60 * 60 * 730;
ctx.Context.Response.Headers[HeaderNames.CacheControl] =
"public,max-age=" + durationInSeconds;
}
});
app.UseSession();
app.UseIdentity();
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseFacebookAuthentication(new FacebookOptions()
{
AppId = Configuration["Authentication:Facebook:AppId"],
AppSecret = Configuration["Authentication:Facebook:AppSecret"]
});
app.UseGoogleAuthentication(new GoogleOptions()
{
ClientId = Configuration["Authentication:Google:ClientId"],
ClientSecret = Configuration["Authentication:Google:ClientSecret"]
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
try
{
await CreateRoles(context, serviceProvider);
}
catch (Exception)
{ }
}
private async Task CreateRoles(ApplicationDbContext context, IServiceProvider serviceProvider)
{
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
// Create a list of roles with both name and normalised name attributes
List<IdentityRole> roles = new List<IdentityRole>
{
new IdentityRole { Name = "Admin", NormalizedName = "ADMIN" },
new IdentityRole { Name = "Member", NormalizedName = "MEMBER" },
new IdentityRole { Name = "Moderator", NormalizedName = "MODERATOR" }
};
// Check if the role already exists
foreach (var role in roles)
{
var roleExist = await RoleManager.RoleExistsAsync(role.Name);
if (!roleExist)
{ // Add it if it doesn't
context.Roles.Add(role);
context.SaveChanges();
}
}
var user = await userManager.FindByEmailAsync("markperry.uk#gmail.com");
if (user != null)
{
var gotRoles = userManager.GetRolesAsync(user);
if (!gotRoles.Equals("Admin"))
{
await userManager.AddToRoleAsync(user, "Admin");
}
}
else if (user == null)
{
var nuser = new ApplicationUser
{
FirstName = Configuration["AppSettings:Admin:FirstName"],
LastName = Configuration["AppSettings:Admin:LastName"],
PhoneNumber = Configuration["AppSettings:Admin:PhoneNumber"],
UserName = Configuration["AppSettings:Admin:UserName"],
Email = Configuration["AppSettings:Admin:Email"],
JoinDate = DateTime.Now,
EmailConfirmed = true,
PhoneNumberConfirmed = true
};
var result = await userManager.CreateAsync(nuser, Configuration["AppSettings:Admin:Password"]);
if (result.Succeeded)
{
await userManager.AddClaimAsync(nuser, new Claim("GivenName", nuser.FirstName));
await userManager.AddClaimAsync(nuser, new Claim("Surname", nuser.LastName));
await userManager.AddToRoleAsync(nuser, "Admin");
}
}
}
}
}
The snippet I added to configure is:
app.UseRewriter(new RewriteOptions()
.AddRedirectToHttps());
which uses Microsoft.AspNetCore.Rewrite;
I have just used chrome to inspect, and that shows repeated redirects, and fails due to "ERR_TOO_MANY_REDIRECTS" so something is causing a loop.
Is there a way to check if the request is already "https", or is there another way I can do things?
After spending the whole day trying to sort this out, adding [RequireHttps] attributes, trying a variety of snippets I found googling the issue, trying to pass headers... In the end I resorted to something I tried earlier that hadn't seemed to have worked. I edited the web.config file that is on the server (I don't know how to do it at publish) adding the following:
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP/S to HTTPS Redirect" enabled="true" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAny">
<add input="{SERVER_PORT_SECURE}" pattern="^0$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
Taken from a comment here: https://github.com/aspnet/KestrelHttpServer/issues/916
From what I've read, its to do with Kestrel, I'm not entirely sure what though :D but it works! It's going to be annoying to have to change this every publish, so tomorrow I'll try and figure out how this can be done for me each time.
I was able to solve a similar issue with the following:
if (env.IsProduction())
{
app.UseRewriter(new RewriteOptions().AddRedirectToHttpsPermanent());
}