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();
});
// …
}
}
Related
I have an API which contains a HostedService that is built in VS2022 with .Net 6.
When I run locally the service is called as expected and everything works fine but when deplyed, the service doesn't seem to start.
I have tried many different configurations and even tried using a background service but it all has the same result. Here is my code:
I have an existing app build in VS2019 .Net Core 3.1 that has a HostedService and is working fine. I noticed that when I converted my .Net Core app to .Net 6, the service did not start when I deployed so I decided to just build a little app to try and find what's causing the issue.
Program.cs
using HostedServices.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, loggerConfiguration) => loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.Enrich.WithMachineName());
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddHostedService<MainService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Here is the Hosted Service
namespace HostedServices.Services
{
public class MainService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<MainService> _logger;
private Timer _timer;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
public MainService(ILogger<MainService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation($"Test Hosted Service Started {DateTime.Now}.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_executingTask = DoWorkAsync(_stoppingCts.Token);
}
private async Task DoWorkAsync(CancellationToken token)
{
_logger.LogInformation(
"Doing work: {0}", DateTime.Now);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
}
Everything runs fine locally but when I deploy it, the service doesn't seem to start, no log files are generated and I can't seem to find any errors reported.
Any ideas?
We had the same problem and resolved it by following IIS settings:
Under "Page" Advanced Settings: "Preload Enabled" must be set to "true" (this forces app to run after every app pool recycle and on startup)
Under "App Pool" Advanced settings: "Start mode" must be set to "AlwaysRunning"
"Page" in IIS must have http allowed (since preload enabled works only on http and not via https)
The problem is that while IHostedService start when the site starts when does IIS start the site? By default, it doesn't until a request is made. If your site goes idle for a while IIS will kill the thread as well.
IIS has a feature you can enable called "Application Initialization".
You then need to configure the Application Pool to always start (startMode="AlwaysRunning").
Lastly, you configure the site's application to preload which sends a fake HTTP request to the site on startup (preloadEnabled="true").
The docs for this are here: https://learn.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-application-initialization
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
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)
How do I read the appsettings.json file in my botframework (v4) app? I see the configuration is set up in the Startup.cs, but how do I access the settings in other classes?
One of the goals of the v4 ASP.NET core integration was to be idiomatic to existing .NET Core patterns. One of the things this means is that when you implement an IBot and add it with AddBot<TBot>, it becomes a participant in dependency injection just like an ASP.NET MVC controller would. This means that any services you might need to access, including configuration types such as IOptions<T>, will be injected into your bot via the constructor if you ask for them.
In this case, you just want to leverage the "options pattern" from the Configuration APIs and that would look something like this:
Startup.cs
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public void ConfigureServices(IServiceCollection services)
{
// Bind MySettings to a section named "mySettings" from config
services.Configure<MySettings>(_configuration.GetSection("mySettings"));
// Add the bot
services.AddBot<MyBot>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseBotFramework();
}
}
MyBot.cs
public class MyBot : IBot
{
private readonly IOptions<MySettings> _mySettings;
public MyBot(IOptions<MySettings> mySettings)
{
_mySettings = mySettings ?? throw new ArgumentNullException(nameof(mySettings));
}
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
// use _mySettings here however you like here
}
}
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
}
}