How to get .NET Core 3.1 login to persist across multiple EC2 instances in Elastic Beanstalk - amazon-ec2

I have a small .NET Core 3.1 app that needs to scale occasionally. I need the logged in users information to persist across any instance that EB spins up. I'd like to do this using the SQL Server RDS we have set up. I've attempted to add the Distributed Sql Server Cache and have set up the table for storing the sessionstate, as described in the documentation... but the login info is not being persisted to this table.
In my Startup.cs ConfigureServices I have:
var sqlSessionConnString = new SqlConnectionStringBuilder(Configuration.GetConnectionString("SqlSession"));
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = sqlSessionConnString.ConnectionString;
options.SchemaName = "dbo";
options.TableName = "TableName";
});
services.AddSession();
When I then log in and check the table there is no data in the table, but my login still works.
What do I have to do to tell Identity to persist the login info in the database instead of in server memory so that my users' login is persisted no matter which instance they are being routed to?

Answering your question: to configure session you also need to add middleware app.UseSession() usage inside your Configure method so
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString =
#"Server=localhost\SQLEXPRESS;Database=master;Trusted_Connection=True;";
options.SchemaName = "dbo";
options.TableName = "TestCache";
});
services.AddSession();
//to inject httpcontet into controller
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSession();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
//your auth settings
//...
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
And now inside your Controller you can save additional values into cache by calling
_distributedCache.SetString("TestString", "TestValue");
or store data to only specific use session by
_context.Session.SetString("name", "John");
And here is a concrete example
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IDistributedCache _distributedCache;
private HttpContext _context;
public WeatherForecastController(ILogger<WeatherForecastController> logger,
IDistributedCache distributedCache,
IHttpContextAccessor httpContextAccessor)
{
_distributedCache = distributedCache;
_context = httpContextAccessor.HttpContext;
}
public string Get()
{
_distributedCache.SetString("TestString", "TestValue");
if (_context.Session.Keys.Contains("name"))
return $"Hello {_context.Session.GetString("name")}";
_context.Session.SetString("name", "John");
return "Session was created";
}
}
You will see that inside SQL table data will be inserted
and session cookies were created (.AspNetCore.Session)

Related

use an in memory collection in dotnet core - how to make available to all classes

IN dotnet core project, how do you make a collection available in memory to any class at any given time?
I was thinking of the following approach:
public interface IInMemoryCache
{
public void Setup();
}
public class InMemoryCache : IInMemoryCache
{
private List<MyObject> cache;
public void Setup() // entered once when app is started
{
cache = new List<MyObject>() {
new MyObject("param1", "param2"),
new MyObject("param3", "param4")
}
public IList<MyObject> GetCollection()
{
return this.collection;
}
}
and then in Startup:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IInMemoryCache cache)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
cache.Setup();
etc
and also:
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IInMemoryCache, InMemoryCache>();
Is this a good approach, does it mean I can inject IInMemoryCache in any class and able to access the cache object? - for the whole lifetime of the app (meaning, while it's up and running, if I restart it, the collection is expected to again initialise from running the Setup method)
So right now in any class I just add: IInMemoryCache cache
and then cache.GetCollection() to retrieve the collection (that was setup ONCE at app startup).
is there a better way like a native feature for caching a collection available to all classes?

BackgroundService with asp.net Core MVC 3.1

Here is what i've done:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHostedService<MyBatch>();
}
public class MyBatch : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
Console.Writeline("test");
await Task.Delay(TimeSpan.FromSeconds(10));
}
}
}
I have 2 problems:
First: The web application does not respond on 5000 port when i launch "dotnet run". If i remove my batch, the 5000 port works again. (test messages are displayed on console)
Second: I want to access to my database with Entity Framework. How can i access to my database context from this batch file ?
Thanks
I want to access to my database with Entity Framework. How can i access to my database context from this batch file ?
To consume a scoped service (such as DbContext etc) from singleton hosted service, you can create a scope then use it to resolve scoped service(s) you need from this scope, like below.
public class MyBatch : BackgroundService
{
public IServiceProvider Services { get; }
public MyBatch(IServiceProvider services)
{
Services = services;
}
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
Console.WriteLine("test");
await Task.Delay(TimeSpan.FromSeconds(10));
await DoWithDb(stopToken);
}
}
private async Task DoWithDb(CancellationToken stoppingToken)
{
//...
using (var scope = Services.CreateScope())
{
var _myDbContext =
scope.ServiceProvider
.GetRequiredService<MyDbContext>();
//code logic here
//...
}
}
}
You can also get detailed information about "consuming a scoped service in a background task" from here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#consuming-a-scoped-service-in-a-background-task

asp.net core session creation with windows authentication

I am using windows authentication for an intranet asp.net core web application running behind IIS.
I'd like to log the event when a user accesses the application and a session is created.
Some of the authentication services provide a way to add an event-handler for events such as "OnSigningIn", "OnSignedIn", "OnTokenvalidated", etc., to options when configuring those services.
Is there a way to do something like that when using Windows Authentication through services.AddAuthentication(IISDefaults.AuthenticationScheme) or services.AddSession(), or some other way?
Windows authentication happens at the IIS level, before ASP.NET Core even sees the request, which is why there aren't any events for it you can handle. Sessions don't have any events either.
You could inject a simple middleware once you inject the logger into Startup,
public class Startup
{
private readonly ILogger<Startup> _logger;
public IConfiguration Configuration { get; }
public Startup(ILogger<Startup> logger, IConfiguration configuration)
{
_logger = logger;
Configuration = configuration;
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// The typical startup crud, then
app.Use(async (context, next) =>
{
int logged = context.Session.GetInt32("Logged") ?? 0;
if (visits == 0 && CheckIfThisRequestNeedsToUseSession(context))
{
// New session
// Log with _logger, then mark so it doesn't repeat
context.Session.SetInt32("IKnowYou", 1);
// ...
}
await next();
});
// …
}
}

How to persist policy authorization results for users in ASP.NET Core, MVC 6?

Currently I have a simple custom policy handler that looks like so:
protected override void Handle(AuthorizationContext context, UserPolicyRequirement requirement)
{
// authorize user against policy requirements
if (_authorizationTask.AuthorizeUserAgainstPolicy(context.User, requirement))
{
// User passed policy req's
context.Succeed(requirement);
}
}
Problem is, this authorization step takes a long time to execute, but this is required in many different areas of the website. Is there any readily available mechanisms to save/cache the results of this policy authorization so that I only need to do this once per session?
I am currently using Windows Authentication, if that helps.
If per session way does not cause any problem, you can use Session to store user data. Simple implementation is something like below:
First you need a service to get user data from any store
public interface IGetUserDataService
{
<type> GetUserData();
}
I assume that there is Session configuration(see) and IGetUserDataService implementation.
Then you need to create a middleware to handle Session
public class SessionMiddleware
{
private readonly RequestDelegate _next;
private readonly IGetUserDataService _getUserDataService;
public SessionMiddleware(RequestDelegate next, IGetUserDataService getUserDataService)
{
_next = next;
_getUserDataService = getUserDataService;
}
public async Task Invoke(HttpContext context)
{
//user data is obtained only once then is stored in Session
if (context.Session.Get("UserData") == null)
{
context.Session.Set("UserData", getUserDataService.GetData());
}
await _next.Invoke(context);
}
}
//In Startup.cs
app.UseMiddleware<SessionMiddleware>();
Finally get and use session data in handler
public class YourHandler : AuthorizationHandler<YourRequirement>
{
private readonly IHttpContextAccessor _accessor;
public YourHandler(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
var userData =(<type>)_accessor.HttpContext.Session.Get("UserData");
// check
}
}

.net 5 MVC 6 web api using existing identityDb for authentication

I am working on having native app be able to authenticate to a web api which uses an existing identity db database created from MVC6. I understand this is not a secure way of doing things as per this post. However, until I can figure out how to get IdentityServer3 working with a database I thought I would try a simple web api that authenticates to a database I already created when I built a standard MVC 6 web app. Here is what I did:
Created an asp.net 5 web api from the template and added the following:
Settings:
appsettings.json I added:
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-TestUsers-eaf0c85f-23e4-4603-97ce-b9f49ee1d167;Trusted_Connection=True;MultipleActiveResultSets=true"
}
},
Startup:
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApiDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
Models:
public class AppUser : IdentityUser
{
}
DBContext:
public class ApiDbContext : IdentityDbContext<AppUser>
{
}
Controller:
private readonly UserManager<AppUser> _userManager;
private readonly SignInManager<AppUser> _signInManager;
private readonly ILogger _logger;
...
public async Task<IEnumerable<string>> Post([FromBody]LoginModel model)
{
if (ModelState.IsValid) {
string user = model.userid;
string passwd = model.password;
var result = await _signInManager.PasswordSignInAsync(model.userid, model.password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
return new string[] { user };
}
else
{
return new string[] { "Failed" };
}
}
else
{
return new string[] { "Incorrect format received"};
}
}
However, it bombs at the _signInManager line with the error:
System.NullReferenceException: Object reference not set to an instance
of an object.
So apparently _signInManager is Null because I know the model is fine because I am printing the userid and password and they are there.
What am I missing so I can use the signInManager in a web api?
I went back yet another time to see what was different between the web api and the web app, since the web app auth was working fine. Here is what I added to get it working:
controller needed a constructor:
public AuthController(
SignInManager<AppUser> signInManager,
ILoggerFactory loggerFactory)
{
_signInManager = signInManager;
_logger = loggerFactory.CreateLogger<AuthController>();
}
Which got rid of my other error but produced the following error:
System.InvalidOperationException: No authentication handler is
configured to handle the scheme: Microsoft.AspNet.Identity.Application
So after researching that I needed to add to startup:
configureservices:
services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<ApiDbContext>()
.AddDefaultTokenProviders();
configure:
app.UseIdentity();
Adding these to the above allowed me to post JSON with userid and password.

Resources