diff --git a/application/account-management/WebApp/global.d.ts b/application/account-management/WebApp/global.d.ts index 70f661c34..28ee1c8d7 100644 --- a/application/account-management/WebApp/global.d.ts +++ b/application/account-management/WebApp/global.d.ts @@ -16,6 +16,8 @@ export declare global { APPLICATION_VERSION: string; /* User locale */ LOCALE: string; + /* AntiForgery token */ + XSRF_TOKEN: string; } /** diff --git a/application/account-management/WebApp/src/lib/api/client.ts b/application/account-management/WebApp/src/lib/api/client.ts index af9b48466..adedae306 100644 --- a/application/account-management/WebApp/src/lib/api/client.ts +++ b/application/account-management/WebApp/src/lib/api/client.ts @@ -2,4 +2,4 @@ import createClient from "openapi-fetch"; import type { paths } from "./api.generated"; const baseUrl = import.meta.env.PUBLIC_URL; -export const accountManagementApi = createClient({ baseUrl }); +export const accountManagementApi = createClient({ baseUrl, headers: { "X-XSRF-TOKEN": import.meta.env.XSRF_TOKEN } }); diff --git a/application/account-management/WebApp/src/ui/AntiForgeryToken.tsx b/application/account-management/WebApp/src/ui/AntiForgeryToken.tsx new file mode 100644 index 000000000..bab1b9b65 --- /dev/null +++ b/application/account-management/WebApp/src/ui/AntiForgeryToken.tsx @@ -0,0 +1,8 @@ +/** + * AntiForgeryToken Hidden Form Element + */ +export function AntiForgeryToken() { + return ( + + ); +} diff --git a/application/shared-kernel/ApiCore/ApiCoreConfiguration.cs b/application/shared-kernel/ApiCore/ApiCoreConfiguration.cs index 7fc40777c..17a828beb 100644 --- a/application/shared-kernel/ApiCore/ApiCoreConfiguration.cs +++ b/application/shared-kernel/ApiCore/ApiCoreConfiguration.cs @@ -74,6 +74,13 @@ public static IServiceCollection AddApiCoreServices(this IServiceCollection serv options.KnownProxies.Clear(); }); + // Enable support for CSRF tokens and configure the header and form field names + services.AddAntiforgery(options => + { + options.HeaderName = "X-XSRF-TOKEN"; + options.FormFieldName = "__RequestVerificationToken"; + }); + builder.AddServiceDefaults(); if (builder.Environment.IsDevelopment()) @@ -135,6 +142,9 @@ public static WebApplication AddApiCoreConfiguration(this WebApplica app.Services.ApplyMigrations(); + // Enable support for CSRF tokens + app.UseAntiforgery(); + return app; } } \ No newline at end of file diff --git a/application/shared-kernel/ApiCore/Middleware/WebAppMiddleware.cs b/application/shared-kernel/ApiCore/Middleware/WebAppMiddleware.cs index 838fd149a..92d05ab5b 100644 --- a/application/shared-kernel/ApiCore/Middleware/WebAppMiddleware.cs +++ b/application/shared-kernel/ApiCore/Middleware/WebAppMiddleware.cs @@ -1,6 +1,7 @@ using System.Security; using System.Text; using System.Text.Json; +using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Json; @@ -18,7 +19,9 @@ public sealed class WebAppMiddleware public const string PublicUrlKey = "PUBLIC_URL"; public const string CdnUrlKey = "CDN_URL"; private const string Locale = "LOCALE"; + private const string XsrfToken = "XSRF_TOKEN"; public const string ApplicationVersion = "APPLICATION_VERSION"; + private readonly IAntiforgery _antiforgery; private readonly string _cdnUrl; private readonly StringValues _contentSecurityPolicy; @@ -35,12 +38,14 @@ public WebAppMiddleware( RequestDelegate next, Dictionary staticRuntimeEnvironment, string htmlTemplatePath, - IOptions jsonOptions + IOptions jsonOptions, + IAntiforgery antiforgery ) { _next = next; _staticRuntimeEnvironment = staticRuntimeEnvironment; _htmlTemplatePath = htmlTemplatePath; + _antiforgery = antiforgery; _jsonSerializerOptions = jsonOptions.Value.SerializerOptions; VerifyRuntimeEnvironment(staticRuntimeEnvironment); @@ -87,10 +92,13 @@ public Task InvokeAsync(HttpContext context) { if (context.Request.Path.ToString().StartsWith("/api/")) return _next(context); + var tokenSet = _antiforgery.GetAndStoreTokens(context); + var cultureFeature = context.Features.Get(); var userCulture = cultureFeature?.RequestCulture.Culture; - var requestEnvironmentVariables = new Dictionary { { Locale, userCulture?.Name ?? "en-US" } }; + var requestEnvironmentVariables = new Dictionary + { { Locale, userCulture?.Name ?? "en-US" }, { XsrfToken, tokenSet.RequestToken! } }; context.Response.Headers.Append("Content-Security-Policy", _contentSecurityPolicy); return context.Response.WriteAsync(GetHtmlWithEnvironment(requestEnvironmentVariables));