I have an ASP.NET Core API \ Angular app. My API needs to support cookies and tokens.
After I login using my service the anti-forgery token returned is not valid as it was created based on a null user. I've tried setting ClaimsPrincipal after my PasswordSignInAsync and regenerating the anti-forgery token (see below) but that still does not work. Any ideas?
public virtual async Task<IActionResult> Login([FromBody] AccountLoginModel model)
{
var result = await this.SignInManager.PasswordSignInAsync(model.Email, model.Password, isPersistent: model.RememberMe, lockoutOnFailure: false);
if (!result.Succeeded)
{
return this.BadRequest();
}
var user = await this.UserManager.FindByEmailAsync(model.Email);
// Must manually set the HttpContext user claims to those of the logged
// in user. Otherwise MVC will still include a XSRF token for the "null"
// user and token validation will fail. (MVC appends the correct token for
// all subsequent reponses but this isn't good enough for a single page
// app.)
var principal = await this.PrincipalFactory.CreateAsync(user);
this.HttpContext.User = principal;
// Update XSRF token
var tokens = this.Antiforgery.GetAndStoreTokens(this.HttpContext);
return this.Ok();
}
ASP.Net Core 2.2 will apparently have a "recommended" approach for this.
But until then this is what I came up with: https://github.com/aspnet/Home/issues/2783#issuecomment-422322294
I'm not using Angular, but I'm using the Axios HTTP Client which has the same "read cookie, write header" support. So this isn't tested with Angular, but it is using the same cookie and header names.
I'm not totally happy with it, because it feels like manually setting HttpContext.User is rather .... hacky.
I created a ResultFilterAction which will set the cookie after Controller Actions have run. Inspiration: https://github.com/aspnet/Home/issues/2415#issuecomment-354674201
public class AntiforgeryCookieResultFilterAttribute : ResultFilterAttribute
{
protected IAntiforgery Antiforgery { get; set; }
public AntiforgeryCookieResultFilterAttribute(IAntiforgery antiforgery) => this.Antiforgery = antiforgery;
public override void OnResultExecuting(ResultExecutingContext context)
{
var tokens = this.Antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
}
}
And I hooked that up in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN";
});
services.AddTransient<AntiforgeryCookieResultFilterAttribute>();
services
//AJ: See above for more information about AntiForgeryTokens.
.AddMvc(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
options.Filters.AddService<AntiforgeryCookieResultFilterAttribute>();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
{...}
}
And finally a line needs adding to the Login and Logout actions to make sure the HttpContext.User is set.
Login: HttpContext.User = await signInManager.CreateUserPrincipalAsync(user);
Logout: HttpContext.User = new ClaimsPrincipal();
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