Implementing cookie-based session authentication in an Angular application with an ASP.NET Core API is a secure and effective choice. It provides CSRF protection, secures session authentication, and mitigates the risk of credential leakage via XSS.
Here is a step-by-step guide to implement this:
Cookies are automatically included in requests to the same origin and can store session information securely. In this approach:
- The server creates a session for the authenticated user.
- A secure, HttpOnly cookie is set in the user's browser, storing the session identifier or authentication token.
-
Install Required Packages Add the necessary NuGet package for cookie authentication:
dotnet add package Microsoft.AspNetCore.Authentication.Cookies
-
Configure Authentication in ASP.NET Core Update the
Program.cs
orStartup.cs
:builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.HttpOnly = true; // Prevent XSS attacks options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // Only send cookies over HTTPS options.Cookie.SameSite = SameSiteMode.Strict; // Prevent CSRF options.ExpireTimeSpan = TimeSpan.FromMinutes(30); // Session expiration options.SlidingExpiration = true; // Renew the cookie on activity options.LoginPath = "/Account/Login"; // Redirect on unauthorized access options.AccessDeniedPath = "/Account/AccessDenied"; // Redirect on forbidden access });
-
Create the Login and Logout Endpoints Create an
AccountController
to handle user login and logout:[ApiController] [Route("api/[controller]")] public class AccountController : ControllerBase { private readonly SignInManager<IdentityUser> _signInManager; public AccountController(SignInManager<IdentityUser> signInManager) { _signInManager = signInManager; } [HttpPost("login")] public async Task<IActionResult> Login([FromBody] LoginModel model) { if (!ModelState.IsValid) return BadRequest(ModelState); var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, isPersistent: false, lockoutOnFailure: false); if (result.Succeeded) { return Ok(new { message = "Login successful" }); } return Unauthorized(new { message = "Invalid credentials" }); } [HttpPost("logout")] public async Task<IActionResult> Logout() { await _signInManager.SignOutAsync(); return Ok(new { message = "Logged out successfully" }); } } public class LoginModel { public string Username { get; set; } public string Password { get; set; } }
CSRF protection is a built-in feature in ASP.NET Core when using cookies. Ensure that:
- The
Cookie.SameSite
option is set toStrict
orLax
. - For cross-site requests, implement an anti-forgery token.
Enable anti-forgery middleware:
services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN"; // Angular reads this header
});
-
Setup HttpClient withCredentials When making API requests, set the
withCredentials
flag to ensure cookies are sent with the request:import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class AuthService { private apiUrl = 'https://yourapi.com/api/account'; constructor(private http: HttpClient) {} login(credentials: { username: string; password: string }) { return this.http.post(`${this.apiUrl}/login`, credentials, { withCredentials: true }); } logout() { return this.http.post(`${this.apiUrl}/logout`, {}, { withCredentials: true }); } getUserData() { return this.http.get(`${this.apiUrl}/userdata`, { withCredentials: true }); } }
-
Handle CSRF Tokens ASP.NET Core can automatically send a CSRF token to Angular. Configure Angular to include this token in all requests:
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable() export class CsrfInterceptor implements HttpInterceptor { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const csrfToken = document.cookie.split('; ').find(row => row.startsWith('XSRF-TOKEN=')); if (csrfToken) { const token = csrfToken.split('=')[1]; request = request.clone({ setHeaders: { 'X-XSRF-TOKEN': token } }); } return next.handle(request); } }
Register the interceptor in
AppModule
:providers: [ { provide: HTTP_INTERCEPTORS, useClass: CsrfInterceptor, multi: true } ];
Use Angular guards to protect routes for authenticated users:
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): boolean {
if (this.authService.isAuthenticated()) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
- Session Timeout: Implement a short session timeout to minimize risk from inactive users.
- HTTP-Only Cookies: Ensure cookies cannot be accessed via JavaScript (
HttpOnly
). - Secure Cookies: Enforce the use of HTTPS with
Secure
cookies. - Content Security Policy (CSP): Prevent XSS attacks by setting a strong CSP.
- Cross-Origin Requests: Set up a strict CORS policy.
This approach leverages the built-in benefits of cookies, ensures CSRF protection, and enhances security against XSS attacks. It works well for applications where server-side authentication logic is a good fit.