I'm presently developing an application with an ASP.NET Core (2.1) API server/backend and an Angular Single Page Application frontend.
For user authentication, I'm using ASP.NET Core Identity, along with several of the default methods provided within AccountController
for login, logout, register, forgot password, etc. My Angular application accesses this API to perform these actions.
The Angular client files are hosted directly out of wwwroot
in the ASP.NET Core application. I am hosting using IIS Express (I have tried IIS as well to no avail) under localhost:5000
.
Authentication is configured in ASP.NET Core's Startup
class with the following:
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = false;
config.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
// Prevent API from redirecting to login or Access Denied screens (Angular handles these).
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = 403;
return Task.CompletedTask;
};
});
Register and Forgot Password both work without a hitch. The API is called and the appropriate actions are taken.
Login appears to work: The method is invoked within Angular:
@Injectable()
export class AuthService extends BaseService {
constructor(private httpClient: HttpClient) {
super(httpClient, `${config.SERVER_API_URL}/account`);
}
// Snip
login(login: Login): Observable<boolean> {
return this.httpClient.post<boolean>(`${this.actionUrl}/Login`, login)
.catch(this.handleError);
}
// Snip
}
The ASP.NET Core API gets the call, logs the user in, and returns success to indicate to Angular that login worked and they should proceed to redirect the user:
namespace ApartmentBonds.Web.Api.Account
{
[Route("api/[controller]/[action]")]
public class AccountController : APIControllerBase
{
// Snip
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginViewModel model, string returnUrl = null)
{
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return Ok(true);
}
}
return Ok(false);
}
// Snip
}
}
The session cookie is created successfully, and I can see it within the browser (using Firefox Quantum in this screenshot, but have also tried in IE, in Firefox incognito, etc):
To test, I have another API method which simply returns whether or not the calling user is signed in:
// Angular
isLoggedIn(): Observable<boolean> {
return this.httpClient.get<boolean>(`${this.actionUrl}/IsLoggedIn`)
.catch(this.handleError);
}
// ASP.NET Core API
[HttpGet]
[AllowAnonymous]
public IActionResult IsLoggedIn()
{
return Ok(_signInManager.IsSignedIn(User));
}
I make this call within the browser, and it returns false. (All other aspects of the site that depend on the user being logged in also show the user is not logged in -- this method is just a quick and dirty way to verify this problem.) Note that the client is correctly sending the Identity cookie along with the request:
Since the only site in question here is localhost:5000
I'm not sure why this is not working.
Things I've tried
services.ConfigureApplicationCookie
options.Events.Cookie.Domain
to localhost:5000
explicitlyoptions.Events.Cookie.Path
to /api
localhost:8888
) after Publishing the Core ApplicationAnyone have any ideas?
In order for authentication handlers to run on the request, you need app.UseAuthentication();
in the middleware pipeline (before middleware that requires it like MVC).