I need to implement custom "authentication" for my company. I say that in quotes because the user technically gets authenticated before it hits the application and if so, the userId will exist in request headers.
What I need to do is figure out a way to query the database and get additional user information based on that Id, and set the HttpContext.User object so that it can be used easily within the application.
The route I am taking now involves using Cookie Authentication without ASP.NET Core Identity. I have combined that idea with custom middleware that will query the database for the user, populate Claims from the db fields, and use the context.SignInAsync to create the cookie. I place this middleware before app.UseAuthentication(). The problem is upon first request the .User object is not set, because it seems the SignIn method only creates the cookie but doesn't set the .User object. The Authentication middleware doesn't yet see the cookie because it does not exist on first request.
Could anyone provide any ideas? Maybe I'm going about it wrong, or this technique is fine but I'm missing what I need to make it work.
in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication("MyAuthenticationCookie")
.AddCookie("MyAuthenticationCookie");
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMyUserMiddleware();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
custom Middleware:
public class MyUserMiddleware
{
private readonly RequestDelegate _next;
public MyUserMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
// Sign in user if this auth cookie doesn't exist
if (context.Request.Cookies[".AspNetCore.MyAuthenticationCookie"] == null)
{
// Get user from db - not done
// Set claims from user object - put in dummy test name for now
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "TEST"),
};
var claimsIdentity = new ClaimsIdentity(claims, "MyAuthenticationCookie");
context.SignInAsync("MyAuthenticationCookie", new ClaimsPrincipal(claimsIdentity));
}
return this._next(context);
}
}
public static class MyUserMiddlewareExtensions
{
public static IApplicationBuilder UseMyUserMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyUserMiddleware>();
}
}
Middleware is software that's assembled into an app pipeline to handle requests and responses. ASP.NET Core provides a rich set of built-in middleware components, but in some scenarios you might want to write a custom middleware. This topic describes how to write convention-based middleware.
There are 3 steps for using cookie authentication. First is to add authentication middleware with the AddAuthentication and AddCookie methods. Secondly, specify the app must use authentication & authorization. Finally apply the [Authorize] attribute on the controllers and actions that require the cookie authorization.
You do not need a separate CookieAuthentication middleware when you are using ASPNET identity. UseIdentity() will do that for you and generate a cookie. You can set the "cookie options" in the AddIdentity block of the application like so: services.
Short answer: you should use a custom AuthorizationHandler
to authenticate & retrieve claims.
Long answer: With ASP.NET CORE you should walk away from authentication middleware. Instead you should use an AuthenticationHandler microsoft
To create a custom Authentication handler, you will need to create a new class inheriting from AuthenticationHandler<TOption>
. TOption
is a simple class used to pass parameters to your handler.
public class TecMobileOptions : AuthenticationSchemeOptions
{
// Add your options here
}
public class MyNewHandler : AuthenticationHandler<MyOptions>
{
private readonly ILogger _logger;
public TecMobileHandler(
IOptionsMonitor<MyOptions> options,
ILoggerFactory loggerFactory,
UrlEncoder encoder,
ISystemClock clock) : base(options, loggerFactory, encoder, clock)
{
// Inject here your DbContext
_logger = loggerFactory.CreateLogger("name...");
}
}
Then you will need to implement the HandleAuthenticateAsync method. It will be called by the Auth middleware when necessary:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authorization = Request.Headers["UserId"].ToString();
(...)
return AuthenticateResult.Success(
new AuthenticationTicket(**your claims**, Scheme.Name));
}
Claims returned by this method will be available through the HttpContext.User object.
Once that done, you will need to add your scheme to the authentication builder.
services.AddAuthentication()
.AddCookie("MyAuthenticationCookie");
.AddScheme<MyOptions, MyHandler>("MyHandlerName");
Don't forget to add in Startup.cs / Configure methods the following code line
app.UseAuthentication();
Finally, you will need to add the Authorize attribute on all classes/methods you want to secure
[Authorize(AuthenticationSchemes = "MyHandlerName")]
public class MyControllerController : BaseController
{ }
OR
[Authorize(AuthenticationSchemes = "MyHandlerName")]
public IActionResult MyMethod()
{ }
EDIT: Here the solution covering the full login process. Let's consider you define two authentication schemes - Cookie based is called CookieScheme - AutoSignInScheme: create the corresponding handler following the steps above
[Authorize(AuthenticationSchemes = "CookieScheme")]
public class SecuredController : Controller
{
(...)
}
Then you will need to add the AccountController
public class AccountController : Controller
{
[HttpGet]
[Authorize(AuthenticationSchemes = "AutoSignInScheme")]
public async Task<IActionResult> AutoSignIn(string returnUrl)
{
await HttpContext.SignInAsync(
"CookieScheme",
new ClaimsPrincipal(new ClaimsIdentity(User.Claims, "CookieScheme")));
return Redirect(returnUrl);
}
}
In your Startup.cs, add the following lines:
services.AddAuthentication()
.AddCookie("CookieScheme", opts =>
{
opts.LoginPath = new PathString("/account/AutoSignIn");
opts.LogoutPath = ** TODO IF REQUIRED **
opts.Cookie.Expiration = TimeSpan.FromHours(8);
})
.AddScheme<MyOptions, MyHandler>("AutoSignInScheme");
When the users tries to access your site, he is redirected to the autosignin controller. Claims are then retrieved from your DB, stored in a cookie and the user is finally redirected to his initial destination!.
Seb
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With