diff --git a/ApiDto/Responses/TokenResponse.cs b/ApiDto/Responses/TokenResponse.cs deleted file mode 100644 index 9761b87..0000000 --- a/ApiDto/Responses/TokenResponse.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Mirea.Api.Dto.Responses; - -/// -/// Provides a JWT and RT token. -/// -public class TokenResponse -{ - /// - /// A JWT token for accessing protected resources. - /// - [Required] - public required string AccessToken { get; set; } - - /// - /// The date and time when the JWT token expires. - /// - /// After this date, a new JWT token must be requested. - [Required] - public required DateTime ExpiresIn { get; set; } -} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs b/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs new file mode 100644 index 0000000..c3e0cf5 --- /dev/null +++ b/Endpoint/Configuration/Core/Middleware/CookieAuthorizationMiddleware.cs @@ -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); + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/V1/AuthController.cs b/Endpoint/Controllers/V1/AuthController.cs index 6081656..2954985 100644 --- a/Endpoint/Controllers/V1/AuthController.cs +++ b/Endpoint/Controllers/V1/AuthController.cs @@ -6,10 +6,10 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; using Mirea.Api.Dto.Common; using Mirea.Api.Dto.Requests; -using Mirea.Api.Dto.Responses; using Mirea.Api.Endpoint.Common.Attributes; using Mirea.Api.Endpoint.Common.Exceptions; using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.Core.Middleware; using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Security.Common.Dto.Requests; using Mirea.Api.Security.Services; @@ -55,6 +55,12 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass SetCookie("user_key", Fingerprint); } + private void SetAuthToken(string value, DateTimeOffset? expires = null) + { + SetCookie(CookieAuthorizationMiddleware.JwtAuthorizationName, value, expires); + SetCookie("user_key", Fingerprint); + } + [ApiExplorerSettings(IgnoreApi = true)] public void OnActionExecuting(ActionExecutingContext context) { @@ -76,17 +82,17 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass /// then generating and returning an authentication token if successful. /// /// The login request containing the username/email and password. - /// A TokenResponse containing the access token and its expiry if successful, otherwise an Unauthorized response. + /// User's AuthRoles. [HttpPost("Login")] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task> Login([FromBody] LoginRequest request) + [BadRequestResponse] + public async Task> Login([FromBody] LoginRequest request) { var userEntity = user.Value; if (!userEntity.Username.Equals(request.Username, StringComparison.OrdinalIgnoreCase) && !userEntity.Email.Equals(request.Username, StringComparison.OrdinalIgnoreCase) || !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 { @@ -96,21 +102,19 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass }, "1"); SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); + SetAuthToken(token.AccessToken, token.AccessExpiresIn); - return Ok(new TokenResponse - { - AccessToken = token.AccessToken, - ExpiresIn = token.AccessExpiresIn - }); + return Ok(AuthRoles.Admin); } /// /// Refreshes the authentication token using the existing refresh token. /// - /// A TokenResponse containing the new access token and its expiry if successful, otherwise an Unauthorized response. + /// User's AuthRoles. [HttpGet("ReLogin")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task> ReLogin() + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> ReLogin() { if (string.IsNullOrEmpty(RefreshToken)) return Unauthorized(); @@ -128,16 +132,13 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass ); SetRefreshToken(token.RefreshToken, token.RefreshExpiresIn); + SetAuthToken(token.AccessToken, token.AccessExpiresIn); - return Ok(new TokenResponse - { - AccessToken = token.AccessToken, - ExpiresIn = token.AccessExpiresIn - }); + return Ok(AuthRoles.Admin); } catch (SecurityException) { - return Unauthorized(); + return Forbid(); } } @@ -152,6 +153,7 @@ public class AuthController(IOptionsSnapshot user, AuthService auth, Pass { SetRefreshToken("", DateTimeOffset.MinValue); SetFirstToken("", DateTimeOffset.MinValue); + SetAuthToken("", DateTimeOffset.MinValue); await auth.LogoutAsync(Fingerprint); diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index 7843cd3..0b39b2a 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -128,16 +128,18 @@ public class Program } app.UseCustomSwagger(app.Services); + app.UseHttpsRedirection(); app.UseMiddleware(); app.UseMiddleware(); + app.UseMiddleware(); + + app.UseAuthentication(); + app.UseAuthorization(); + app.UseMiddleware(); app.UseMiddleware(); - app.UseHttpsRedirection(); - - app.UseAuthorization(); - app.MapControllers(); app.Run();