sec: move token from responce to cookie

This commit is contained in:
Polianin Nikita 2024-10-09 03:00:26 +03:00
parent b49df925d4
commit 1f3aaca3cf
4 changed files with 43 additions and 45 deletions

View File

@ -1,23 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Mirea.Api.Dto.Responses;
/// <summary>
/// Provides a JWT and RT token.
/// </summary>
public class TokenResponse
{
/// <summary>
/// A JWT token for accessing protected resources.
/// </summary>
[Required]
public required string AccessToken { get; set; }
/// <summary>
/// The date and time when the JWT token expires.
/// </summary>
/// <remarks>After this date, a new JWT token must be requested.</remarks>
[Required]
public required DateTime ExpiresIn { get; set; }
}

View File

@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Http;
using Mirea.Api.Security.Common.Interfaces;
using System.Threading.Tasks;
namespace Mirea.Api.Endpoint.Configuration.Core.Middleware;
public class CookieAuthorizationMiddleware(RequestDelegate next)
{
public const string JwtAuthorizationName = "_ajwt";
public async Task InvokeAsync(HttpContext context, IRevokedToken revokedTokenStore)
{
if (context.Request.Cookies.ContainsKey(JwtAuthorizationName))
context.Request.Headers.Authorization = "Bearer " + context.Request.Cookies[JwtAuthorizationName];
await next(context);
}
}

View File

@ -6,10 +6,10 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Common;
using Mirea.Api.Dto.Requests; using Mirea.Api.Dto.Requests;
using Mirea.Api.Dto.Responses;
using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Attributes;
using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.Exceptions;
using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Core.Middleware;
using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Security.Common.Dto.Requests; using Mirea.Api.Security.Common.Dto.Requests;
using Mirea.Api.Security.Services; using Mirea.Api.Security.Services;
@ -55,6 +55,12 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
SetCookie("user_key", Fingerprint); SetCookie("user_key", Fingerprint);
} }
private void SetAuthToken(string value, DateTimeOffset? expires = null)
{
SetCookie(CookieAuthorizationMiddleware.JwtAuthorizationName, value, expires);
SetCookie("user_key", Fingerprint);
}
[ApiExplorerSettings(IgnoreApi = true)] [ApiExplorerSettings(IgnoreApi = true)]
public void OnActionExecuting(ActionExecutingContext context) public void OnActionExecuting(ActionExecutingContext context)
{ {
@ -76,17 +82,17 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
/// then generating and returning an authentication token if successful. /// then generating and returning an authentication token if successful.
/// </summary> /// </summary>
/// <param name="request">The login request containing the username/email and password.</param> /// <param name="request">The login request containing the username/email and password.</param>
/// <returns>A TokenResponse containing the access token and its expiry if successful, otherwise an Unauthorized response.</returns> /// <returns>User's AuthRoles.</returns>
[HttpPost("Login")] [HttpPost("Login")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)] [BadRequestResponse]
public async Task<ActionResult<TokenResponse>> Login([FromBody] LoginRequest request) public async Task<ActionResult<AuthRoles>> Login([FromBody] LoginRequest request)
{ {
var userEntity = user.Value; var userEntity = user.Value;
if (!userEntity.Username.Equals(request.Username, StringComparison.OrdinalIgnoreCase) && if (!userEntity.Username.Equals(request.Username, StringComparison.OrdinalIgnoreCase) &&
!userEntity.Email.Equals(request.Username, StringComparison.OrdinalIgnoreCase) || !userEntity.Email.Equals(request.Username, StringComparison.OrdinalIgnoreCase) ||
!passwordService.VerifyPassword(request.Password, userEntity.Salt, userEntity.PasswordHash)) !passwordService.VerifyPassword(request.Password, userEntity.Salt, userEntity.PasswordHash))
return Unauthorized("Invalid username/email or password"); return BadRequest("Invalid username/email or password");
var token = await auth.GenerateAuthTokensAsync(new TokenRequest var token = await auth.GenerateAuthTokensAsync(new TokenRequest
{ {
@ -96,21 +102,19 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
}, "1"); }, "1");
SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn);
SetAuthToken(token.AccessToken, token.AccessExpiresIn);
return Ok(new TokenResponse return Ok(AuthRoles.Admin);
{
AccessToken = token.AccessToken,
ExpiresIn = token.AccessExpiresIn
});
} }
/// <summary> /// <summary>
/// Refreshes the authentication token using the existing refresh token. /// Refreshes the authentication token using the existing refresh token.
/// </summary> /// </summary>
/// <returns>A TokenResponse containing the new access token and its expiry if successful, otherwise an Unauthorized response.</returns> /// <returns>User's AuthRoles.</returns>
[HttpGet("ReLogin")] [HttpGet("ReLogin")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<TokenResponse>> ReLogin() [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<AuthRoles>> ReLogin()
{ {
if (string.IsNullOrEmpty(RefreshToken)) if (string.IsNullOrEmpty(RefreshToken))
return Unauthorized(); return Unauthorized();
@ -128,16 +132,13 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
); );
SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn);
SetAuthToken(token.AccessToken, token.AccessExpiresIn);
return Ok(new TokenResponse return Ok(AuthRoles.Admin);
{
AccessToken = token.AccessToken,
ExpiresIn = token.AccessExpiresIn
});
} }
catch (SecurityException) catch (SecurityException)
{ {
return Unauthorized(); return Forbid();
} }
} }
@ -152,6 +153,7 @@ public class AuthController(IOptionsSnapshot<Admin> user, AuthService auth, Pass
{ {
SetRefreshToken("", DateTimeOffset.MinValue); SetRefreshToken("", DateTimeOffset.MinValue);
SetFirstToken("", DateTimeOffset.MinValue); SetFirstToken("", DateTimeOffset.MinValue);
SetAuthToken("", DateTimeOffset.MinValue);
await auth.LogoutAsync(Fingerprint); await auth.LogoutAsync(Fingerprint);

View File

@ -128,16 +128,18 @@ public class Program
} }
app.UseCustomSwagger(app.Services); app.UseCustomSwagger(app.Services);
app.UseHttpsRedirection();
app.UseMiddleware<CustomExceptionHandlerMiddleware>(); app.UseMiddleware<CustomExceptionHandlerMiddleware>();
app.UseMiddleware<MaintenanceModeMiddleware>(); app.UseMiddleware<MaintenanceModeMiddleware>();
app.UseMiddleware<CookieAuthorizationMiddleware>();
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<JwtRevocationMiddleware>(); app.UseMiddleware<JwtRevocationMiddleware>();
app.UseMiddleware<CacheMaxAgeMiddleware>(); app.UseMiddleware<CacheMaxAgeMiddleware>();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers(); app.MapControllers();
app.Run(); app.Run();