Integrer l'authentification Keycloak dans l'application
This commit is contained in:
173
ChessCubing.Server/Program.cs
Normal file
173
ChessCubing.Server/Program.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System.Security.Claims;
|
||||
using ChessCubing.Server.Auth;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddOptions<KeycloakAuthOptions>()
|
||||
.Configure<IConfiguration>((options, configuration) =>
|
||||
{
|
||||
options.BaseUrl = configuration["KEYCLOAK_BASE_URL"] ?? options.BaseUrl;
|
||||
options.Realm = configuration["KEYCLOAK_REALM"] ?? options.Realm;
|
||||
options.ClientId = configuration["KEYCLOAK_CLIENT_ID"] ?? options.ClientId;
|
||||
options.AdminRealm = configuration["KEYCLOAK_ADMIN_REALM"] ?? options.AdminRealm;
|
||||
options.AdminClientId = configuration["KEYCLOAK_ADMIN_CLIENT_ID"] ?? options.AdminClientId;
|
||||
options.AdminUsername = configuration["KEYCLOAK_ADMIN_USERNAME"] ?? options.AdminUsername;
|
||||
options.AdminPassword = configuration["KEYCLOAK_ADMIN_PASSWORD"] ?? options.AdminPassword;
|
||||
});
|
||||
|
||||
builder.Services
|
||||
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.Cookie.Name = "chesscubing.auth";
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
||||
options.SlidingExpiration = true;
|
||||
options.ExpireTimeSpan = TimeSpan.FromDays(7);
|
||||
options.Events = new CookieAuthenticationEvents
|
||||
{
|
||||
OnRedirectToLogin = context =>
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnRedirectToAccessDenied = context =>
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddHttpClient<KeycloakAuthService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapGet("/api/health", () => TypedResults.Ok(new { status = "ok" }));
|
||||
|
||||
app.MapGet("/api/auth/session", (ClaimsPrincipal user) =>
|
||||
TypedResults.Ok(AuthSessionResponse.FromUser(user)));
|
||||
|
||||
app.MapPost("/api/auth/login", async Task<IResult> (
|
||||
LoginRequest request,
|
||||
HttpContext httpContext,
|
||||
KeycloakAuthService keycloak,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
|
||||
{
|
||||
return TypedResults.BadRequest(new ApiErrorResponse("Nom d'utilisateur et mot de passe obligatoires."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var userInfo = await keycloak.LoginAsync(request.Username.Trim(), request.Password, cancellationToken);
|
||||
await SignInAsync(httpContext, userInfo);
|
||||
return TypedResults.Ok(AuthSessionResponse.FromUser(httpContext.User));
|
||||
}
|
||||
catch (KeycloakAuthException exception)
|
||||
{
|
||||
return TypedResults.Json(new ApiErrorResponse(exception.Message), statusCode: exception.StatusCode);
|
||||
}
|
||||
});
|
||||
|
||||
app.MapPost("/api/auth/register", async Task<IResult> (
|
||||
RegisterRequest request,
|
||||
HttpContext httpContext,
|
||||
KeycloakAuthService keycloak,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Username) ||
|
||||
string.IsNullOrWhiteSpace(request.Email) ||
|
||||
string.IsNullOrWhiteSpace(request.Password))
|
||||
{
|
||||
return TypedResults.BadRequest(new ApiErrorResponse("Nom d'utilisateur, email et mot de passe obligatoires."));
|
||||
}
|
||||
|
||||
if (!string.Equals(request.Password, request.ConfirmPassword, StringComparison.Ordinal))
|
||||
{
|
||||
return TypedResults.BadRequest(new ApiErrorResponse("Les mots de passe ne correspondent pas."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var userInfo = await keycloak.RegisterAsync(request with
|
||||
{
|
||||
Username = request.Username.Trim(),
|
||||
Email = request.Email.Trim(),
|
||||
FirstName = request.FirstName?.Trim(),
|
||||
LastName = request.LastName?.Trim(),
|
||||
}, cancellationToken);
|
||||
|
||||
await SignInAsync(httpContext, userInfo);
|
||||
return TypedResults.Ok(AuthSessionResponse.FromUser(httpContext.User));
|
||||
}
|
||||
catch (KeycloakAuthException exception)
|
||||
{
|
||||
return TypedResults.Json(new ApiErrorResponse(exception.Message), statusCode: exception.StatusCode);
|
||||
}
|
||||
});
|
||||
|
||||
app.MapPost("/api/auth/logout", async Task<IResult> (HttpContext httpContext) =>
|
||||
{
|
||||
await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return TypedResults.Ok(AuthSessionResponse.FromUser(new ClaimsPrincipal(new ClaimsIdentity())));
|
||||
});
|
||||
|
||||
app.Run();
|
||||
|
||||
static async Task SignInAsync(HttpContext httpContext, KeycloakUserInfo userInfo)
|
||||
{
|
||||
var claims = new List<Claim>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userInfo.Subject))
|
||||
{
|
||||
claims.Add(new Claim("sub", userInfo.Subject));
|
||||
claims.Add(new Claim(ClaimTypes.NameIdentifier, userInfo.Subject));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userInfo.PreferredUsername))
|
||||
{
|
||||
claims.Add(new Claim("preferred_username", userInfo.PreferredUsername));
|
||||
claims.Add(new Claim(ClaimTypes.Name, userInfo.PreferredUsername));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userInfo.Name))
|
||||
{
|
||||
claims.Add(new Claim("name", userInfo.Name));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userInfo.Email))
|
||||
{
|
||||
claims.Add(new Claim("email", userInfo.Email));
|
||||
claims.Add(new Claim(ClaimTypes.Email, userInfo.Email));
|
||||
}
|
||||
|
||||
foreach (var role in userInfo.Roles.Where(role => !string.IsNullOrWhiteSpace(role)))
|
||||
{
|
||||
claims.Add(new Claim("role", role));
|
||||
claims.Add(new Claim(ClaimTypes.Role, role));
|
||||
}
|
||||
|
||||
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
await httpContext.SignInAsync(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
principal,
|
||||
new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = true,
|
||||
AllowRefresh = true,
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.AddDays(7),
|
||||
});
|
||||
|
||||
httpContext.User = principal;
|
||||
}
|
||||
Reference in New Issue
Block a user