ASP.NET Core session authentication - c#

I'm developing an ASP.NET Core 2.2 web application. I want to store user claims in application memory, not in cookies.
I add AddDistributedMemoryCache, AddSession and UseSession as described here, but when page is requested, I still see cookie data sent to server and received from the server.
My Startup class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
// Make the session cookie essential
options.Cookie.IsEssential = true;
});
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.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSession();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
}
Cookie data:
cookie: .AspNet.Consent=yes; .AspNetCore.Antiforgery.WGD7B_OvtEU=CfDJ8K-fe5FucclFpIYHgLvWwKY0RPLnAZQX6JnN1muQjDbx0UK4C310gp8L2RHdWlsHmoYJihQxtGuUB5GNG742rl7N-UgTynSNz09Jsb11kVgRcxAgQk5yaZnbcaQGQ0tiJUCoKAdwxEgykc2Fc3-vVCY; .AspNetCore.Identity.Application=CfDJ8K-fe5FucclFpIYHgLvWwKaod7oR4502P-cppU0aQI_WYHsvaTEL-Y5Ca1hnJOBznUpadpPkq5ubrH04UhMBpXTnK1ASjuMXMPBhr3PKqPSnXPYPFmhgki1_RicCVDQyl7mRYuWPUY2RjVkgoEIXCBj96zCRK9PWZo0N6N4hAETl-z0LAExj1Sjo6Xz3uWvHsg5GtJijlQmE6BjSh0ObMulxgDFJZEw13IbWJmlLFv7kdvs9va59wBPlEhHFER1Rs0iKW2cpVqQTPK7SjgQrSlo8_KQYHWzYa3xFSjuhrWJnm-Y4u9jXA6yCoaVxG1U-1EbOaQRfUXFs2F9IX6dU7iExsNqhPR4o2CKlt6ERI0JT_p7jHv0hrHbBiUjUVMYi_qoAQRv1OXfVZBLkoRve20gvjQtD3aRZFZR5poX-bq0pw6CNBTLexzD_bU1jJnpaf61OKbQM2-qJnWPS7YayFjJt3k_qALbnquUsSBMDMm3PoFcU26_Ubyu6RTZ-aanKc1bdcEA5o3WF8JksZkrvRFhZZuvWahDpnQCxxy-rELKwXcybcWHi-QB7gxSm6Q6S84NX2390mbHVJ1RO8eUmUF4
How can I make it store only in memory, not in cookies?

You need to set SessionStore for identity cookie authentication, so your authentication cookie is only an identifier.
Instead of
services.AddDefaultIdentity<IdentityUser>()
Use this
services.AddAuthentication(o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
}).AddIdentityCookies(o =>
{
o.ApplicationCookie.PostConfigure(cookie => cookie.SessionStore = new MemoryCacheTicketStore());
});
services.AddIdentityCore<IdentityUser>(o =>
{
o.Stores.MaxLengthForKeys = 128;
}).AddDefaultUI()
.AddDefaultTokenProviders();
MemoryCacheTicketStore.cs
public class MemoryCacheTicketStore : ITicketStore
{
private const string KeyPrefix = "AuthSessionStore-";
private IMemoryCache _cache;
public MemoryCacheTicketStore()
{
_cache = new MemoryCache(new MemoryCacheOptions());
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var guid = Guid.NewGuid();
var key = KeyPrefix + guid.ToString();
await RenewAsync(key, ticket);
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new MemoryCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
options.SetSlidingExpiration(TimeSpan.FromHours(1)); // TODO: configurable.
_cache.Set(key, ticket, options);
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
_cache.TryGetValue(key, out ticket);
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
_cache.Remove(key);
return Task.FromResult(0);
}
}

Related

security page filter for jost many pages

i create a page filter in asp.net core razor page
I want it to be only for those handlers that are inside the administration area
this is my pageFilter
namespace ServiceHost
{
public class SecurityPageFilter :IPageFilter
{
private readonly IAuthHelper _authHelper;
public SecurityPageFilter(IAuthHelper authHelper)
{
_authHelper = authHelper;
}
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
var handlerCompulsoryPermission = (NeedPermissionAttribute)context.HandlerMethod.MethodInfo.GetCustomAttribute(typeof(NeedPermissionAttribute));
var accountPermissions = _authHelper.CurrentAccountPermissions();
if (handlerCompulsoryPermission == null)
return;
if (!_authHelper.IsAuthenticated())
context.HttpContext.Response.Redirect("/Account");
if (!accountPermissions.Contains(handlerCompulsoryPermission.Permission))
context.HttpContext.Response.Redirect("/Account");
}
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
}
}
and this is my startup file
namespace ServiceHost
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
var connectionString = Configuration.GetConnectionString("Keyson_Shop");
ShopManagementBootstrapper.Configure(services, connectionString);
DiscountManagementBootstrapper.Configure(services, connectionString);
InventoryManagementBootstrapper.Configure(services, connectionString);
BlogManagementBootstrapper.Configure(services, connectionString);
CommentManagementBootstrapper.Configure(services, connectionString);
AccountManagementBootstrapper.Configure(services, connectionString);
services.AddTransient<IZarinPalFactory, ZarinPalFactory>();
services.Configure<CookiePolicyOptions>(options =>
{
//this line does access to tempData work
// options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>
{
o.LoginPath = new PathString("/Account");
o.LogoutPath = new PathString("/Account");
o.AccessDeniedPath = new PathString("/AccessDenied");
});
services.AddCors(options => options.AddPolicy("MyPolicy", builder =>
builder
.WithOrigins("https://localhost:5002")
.AllowAnyHeader()
.AllowAnyMethod()));
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add<SecurityPageFilter>();
});
services.AddTransient<IMenuQuery, MenuQuery>();
services.AddTransient<IFileUploader, FileUploader>();
services.AddSingleton<IPasswordHasher, PasswordHasher>();
services.AddTransient<IAuthHelper, AuthHelper>();
services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.BasicLatin,UnicodeRanges.Arabic));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapDefaultControllerRoute();
});
}
}
}
i want to know page filter has any option to give that and just run in my administration areas handler.
but if that hasn't an option what should i need to do about this problem
i get happy if answer this question

Use different aplication cookie or cookie options edit when login through api

I have a problem about api login with cookies, the goal is that before the user can call any api, the user must first call /api/Authentication/Login and then he can use the rest of the operations. However, when I call for example /api/Batch, instead of returning AccessDenied, I get the full html of the login page in the body of the response which is wrong in this case.
services.AddScoped<IAuthenticationService, LdapAuthenticationService>();
services.AddHttpContextAccessor();
services.AddIdentity<User, Role>(o => {
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 8;
o.Password.RequireLowercase = true;
o.Password.RequireDigit = true;
o.Password.RequireUppercase = true;
}).AddEntityFrameworkStores<ScanLinkContext>().AddDefaultTokenProviders().AddRoles<Role>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/SignIn";
});
services
.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = ".AspNetCore.Cookies";
options.Cookie.HttpOnly = true;
options.LoginPath = "/Account/SignIn"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
options.LogoutPath = "/Account/SignOut"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
options.AccessDeniedPath = "/Account/SignIn"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
options.SlidingExpiration = true;
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
});
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WEBAPI v1"));
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseSession();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStatusCodePagesWithRedirects("/Errors/{0}");
app.UseRouting();
// Add authentication to request pipeline
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
}
I need to leave the original configuration, so that it is functional for the web and in the case of visiting a page to which the user has the right, it redirects him to the login page, but for the cookie I would need a different behavior as described above
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class BatchController : ControllerBase
{
private readonly ScanLinkContext _scanlinkContext;
public BatchController( ScanLinkContext scanLinkContext)
{
_scanlinkContext = scanLinkContext;
}
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[Route("Import")]
[HttpPost]
public async Task<StatusCodeResult> PostImport([FromBody] string xml)
{
return Ok();
}
[HttpPut("[action]/{batchName}")]
public async Task<StatusCodeResult> Tranform(string batchName, [FromBody] string json)
{
return Ok();
}
[HttpPut("[action]/{batchName}")]
public async Task<StatusCodeResult> Validate(string batchName, [FromBody] string json)
{
return Ok();
}
[HttpPut("[action]/{batchName}")]
public async Task<StatusCodeResult> Unlock(string batchName)
{
return Ok();
}
[HttpDelete("{batchName}")]
public async Task<StatusCodeResult> Delete(string batchName)
{
return Ok();
}
}
How should I adjust the configuration to achieve the goal?
You have to override the Events.OnRedriectLogin like this.
.ConfigureApplicationCookie(options =>
{
//other options...
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
}

Cookie not expiring per StartUp.cs settings

I have tried a plethora of solutions over the course of 2 days and still have not been able to get this to work. What I want is for a user cookie to expire after a set amount of time
E.g. User A logs in and goes to home page, User A goes for a lunch break. User A comes back and clicks on the nav bar and gets redirected to the login page.
I have tried everything from AddAuthentication(), AddSession() and AddCookie() options all having an ExpireTimeSpan and Cookie.Expiration of my choosing. Nothing seems to work. The project uses ASP.NET Identity and I am aware this service should be called before the cookie options. Please see my current StartUp.cs below, this is the last thing i tried:
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public IContainer ApplicationContainer { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
//other services e.g. interfaces etc.
services.AddAuthentication().AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.Expiration = TimeSpan.FromSeconds(60);
options.LoginPath = "/Account/Login";
options.LogoutPath = "/Account/Logout";
options.AccessDeniedPath = "/AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromSeconds(5);
options.SlidingExpiration = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//services.AddSession();
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
this.ApplicationContainer = containerBuilder.Build();
var serviceProvider = new AutofacServiceProvider(this.ApplicationContainer);
return serviceProvider;
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.ConfigureCustomExceptionMiddleware();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
//app.UseSession();
app.UseMvc();
}
}
The following code isn't affecting the Identity cookie:
services.AddAuthentication().AddCookie(options => ...);
Instead, it's adding a new cookie-based authentication scheme, named Cookies, and configuring that. With all the standard Identity setup, this scheme is unused, so any changes to its configuration will have no effect.
The primary authentication scheme used by Identity is named Identity.Application and is registered inside of the AddIdentity<TUser, TRole> method in your example. This can be configured using ConfigureApplicationCookie. Here's an example:
services.ConfigureApplicationCookie(options => ...);
With that in place, the cookie options will be affected as intended, but in order to set a cookie with a non-session lifetime, you also need to set isPersistent to true inside your call to PasswordSignInAsync. Here's an example:
await signInManager.PasswordSignInAsync(
someUser, somePassword, isPersistent: true, lockoutOnFailure: someBool);

How to customize string root in PhisicalFilePRovider initialization?

In my asp.net core 2.2 web application, in Start-Up class, in Configure method, i set the use of static files, mapping a specified disk folder, instancing a file provider thus:
FileProvider = new PhysicalFileProvider(#"C:\business\ROM\documents\112233\")
like in this code:
using ...
namespace MyApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
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.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(#"C:\business\ROM\documents\112233\"),
RequestPath = new PathString("/dexml")
});
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
When i list my etntities, in the foreach cycle in the view i link the documents thus:
<a target="_blank" href="/dexml/#Html.DisplayFor(modelItem => item.nomefile)">view document</a>
All works fine!
But i need to set PhisicalFileProvider string root with placeholder, like this:
FileProvider = new PhysicalFileProvider(#"C:\business\[code1]\documents\[code2]\")
and i associated code1 and code2 in custom asp.net user identity fields.
How i can set FileProvider string root with placeholder and replace it in same place with user attributes?
thanks
For PhysicalFileProvider, it will be initialized while calling app.UseStaticFiles and could not changed during runtime.
You need to implement your own middleware to serve static files dynamically.
app.UseAuthentication();
app.Map("/dexml", subApp => {
subApp.Use(async (context, next) =>
{
await next();
var userName = context?.User?.Identity?.Name;
if (userName != null)
{
var dbContext = context.RequestServices.GetRequiredService<ApplicationDbContext>();
var user = dbContext.Users.FirstOrDefault(u => u.UserName == userName);
using (var provider = new PhysicalFileProvider($#"D:\{ user.Code1 }\{ user.Code2 }"))
{
var filePath = context.Request.Path.Value.TrimStart('/');
var info = provider.GetFileInfo(filePath);
using (var stream = info.CreateReadStream())
{
var originalStream = context.Response.Body;
await stream.CopyToAsync(originalStream);
context.Response.Body = stream;
}
}
}
});
});

Asp.Net Core | Extend windows auth identity object

I want to use Windows Auth in my intranet application, but I need to extend the identity object to get some extra data. As of now, I only have access to the domain name in the identity user. I tried to implement my own user/role store in order to intercept the authorization calls then use the domain name to go to our database and grab the extra data. I implemented my own store, but none of the methods seem to be called. How do I intercept when the app authorized the window user so that I can go to our database and grab what I need to put in the user object?
Here's my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
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.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddIdentity<MyUser, IdentityRole>()
.AddUserStore<MyUserStore>()
.AddRoleStore<MyRoleStore>()
.AddDefaultTokenProviders();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
What I did was deleting the basic authentication from MVC and added my AuthenticationHandler which extends the AuthenticationService because I don't want to reinvent every method from IAuthenticationService so:
public void ConfigureServices(IServiceCollection services)
{
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.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddIdentity<MyUser, IdentityRole>()
.AddUserStore<MyUserStore>()
.AddRoleStore<MyRoleStore>()
.AddDefaultTokenProviders();
services.Remove(services.FirstOrDefault(x => x.ServiceType == typeof(IAuthenticationService)));
services.Add(new ServiceDescriptor(typeof(IAuthenticationService),typeof(AuthenticationHandler), ServiceLifetime.Scoped));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
And then
public class AuthenticationHandler : AuthenticationService
{
private readonly ILdapRepository _ldapRepository;
public AuthenticationHandler(ILdapRepository ldapRepository,
IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers,
IClaimsTransformation transform) : base(schemes, handlers, transform)
{
_ldapRepository = ldapRepository;
}
public async override Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
{
var idk = await base.AuthenticateAsync(context, scheme);
if (idk.Succeeded) {
var claims = _ldapRepository.LoadClaimsFromActiveDirectory(idk.Principal.Claims.FirstOrDefault(x => x.Type == CustomClaimTypes.Name)?.Value);
idk.Principal.AddIdentity(claims);
}
return idk;
}
}
LdapRepository is nothing else as the DirectoryEntry and DirectorySearcher for active directory class.
I hope this helps you.

Categories

Resources