Add support for cookie-based JWT authentication alongside the existing Authorization header.

This commit is contained in:
Felipe Cotti 2024-11-17 23:42:46 -03:00
parent 2f0d63a90f
commit eaecace48d
5 changed files with 62 additions and 6 deletions

View file

@ -0,0 +1,8 @@
using Guestbooky.API.Enums;
namespace Guestbooky.API.Configurations;
public class APISettings
{
public required RunningEnvironment RunningEnvironment { get; set; }
}

View file

@ -1,4 +1,5 @@
using Guestbooky.API.DTOs.Auth;
using Guestbooky.API.Configurations;
using Guestbooky.API.DTOs.Auth;
using Guestbooky.Application.UseCases.AuthenticateUser;
using Guestbooky.Application.UseCases.RefreshToken;
using MediatR;
@ -12,11 +13,13 @@ public class AuthController : ControllerBase
{
private readonly IMediator _mediator;
private readonly ILogger<AuthController> _logger;
private readonly APISettings _apiSettings;
public AuthController(IMediator mediator, ILogger<AuthController> logger)
public AuthController(IMediator mediator, ILogger<AuthController> logger, APISettings apiSettings)
{
_mediator = mediator;
_logger = logger;
_apiSettings = apiSettings;
}
[ProducesResponseType(typeof(LoginResponseDto), 200)]
@ -35,6 +38,7 @@ public class AuthController : ControllerBase
if(result.IsAuthenticated)
{
_logger.LogInformation("Authentication successful. Returning LoginResponse.");
SetJwtCookie(result.Token);
return Ok(new LoginResponseDto(result.Token, result.RefreshToken));
}
else
@ -80,4 +84,16 @@ public class AuthController : ControllerBase
return Problem($"An error occurred on the server: {e.Message}", statusCode: StatusCodes.Status500InternalServerError);
}
}
private void SetJwtCookie(string token)
{
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = _apiSettings.RunningEnvironment == Enums.RunningEnvironment.Production,
SameSite = SameSiteMode.Strict,
Expires = DateTimeOffset.UtcNow.AddHours(2)
};
Response.Cookies.Append("token", token, cookieOptions);
}
}

View file

@ -0,0 +1,8 @@
namespace Guestbooky.API.Enums;
public enum RunningEnvironment
{
Unknown = 0,
Development,
Production
}

View file

@ -1,11 +1,13 @@
using Guestbooky.Application.DependencyInjection;
using Guestbooky.Infrastructure.DependencyInjection;
using Guestbooky.Infrastructure.Environment;
using Guestbooky.API.Configurations;
using Guestbooky.API.Enums;
using Guestbooky.API.Validations;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Serilog;
using Guestbooky.API.Validations;
namespace Guestbooky.API
{
@ -74,9 +76,10 @@ namespace Guestbooky.API
var corsOrigins = builder.Configuration[Constants.CORS_ORIGINS]?.Split(',') ?? Array.Empty<string>();
cfg.AddPolicy(name: "local", policy =>
{
policy.WithExposedHeaders("Content-Range", "Accept-Ranges")
policy.WithExposedHeaders("Content-Range", "Accept-Ranges", "Set-Cookie")
.WithOrigins(corsOrigins)
.AllowAnyHeader()
.AllowCredentials()
.WithMethods("GET", "POST", "DELETE", "OPTIONS");
});
});
@ -86,7 +89,11 @@ namespace Guestbooky.API
options.InvalidModelStateResponseFactory = InvalidModelStateResponseFactory.DefaultInvalidModelStateResponse;
});
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
builder.Services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.RequireHttpsMetadata = false;
@ -102,12 +109,22 @@ namespace Guestbooky.API
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
o.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["token"];
return Task.CompletedTask;
}
};
});
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddApplication();
builder.Services.AddSingleton(new APISettings(){ RunningEnvironment = GetRunningEnvironment(builder.Configuration["ASPNETCORE_ENVIRONMENT"]!) });
if (builder.Environment.IsDevelopment())
{
@ -170,5 +187,12 @@ namespace Guestbooky.API
_ => conf.MinimumLevel.Information()
};
public static RunningEnvironment GetRunningEnvironment(string env) => env.ToUpper() switch
{
"DEVELOPMENT" => RunningEnvironment.Development,
"PRODUCTION" => RunningEnvironment.Production,
_ => RunningEnvironment.Unknown
};
}
}

View file

@ -53,7 +53,7 @@ namespace Guestbooky.Infrastructure.Persistence.Repositories
var messageDtos = await _messages.Find(_ => true)
.SortBy(x => x.Timestamp)
.Skip((int?)offset)
.Limit(50)
.Limit(10)
.ToCursorAsync(cancellationToken);
return messageDtos.ToEnumerable().Select(MapToDomainModel).ToArray();
}