Enhance Auth Routes with CSRF
Store and clear CSRF secrets in your application sessions.
Your C# server maintains each user's CSRF secret in their application session, which is used for cryptographically signing any generated CSRF tokens.
Now that your CSRF middleware is installed, add CSRF management logic into your auth routes.
Callback Endpoint
Your Callback endpoint should use the helper function to persist a CSRF secret into the session and set the CSRF cookie in the response so that all subsequent requests can properly validate CSRF state.
// AuthRoutes.cs
...
app.MapGet("/auth/callback", async (HttpContext httpContext, IWristbandAuthService wristbandAuth) =>
{
try
{
var callbackResult = await wristbandAuth.Callback(httpContext);
if (callbackResult.Result == CallbackResultType.REDIRECT_REQUIRED)
{
return Results.Redirect(callbackResult.RedirectUrl);
}
var userinfo = callbackData.Userinfo;
var claims = new List<Claim>
{
new("isAuthenticated", "true"),
new("accessToken", callbackData.AccessToken),
new("refreshToken", callbackData.RefreshToken ?? string.Empty),
// Convert expiration seconds to a Unix timestamp in milliseconds.
new("expiresAt", $"{DateTimeOffset.Now.ToUnixTimeMilliseconds() + (callbackData.ExpiresIn * 1000)}"),
new("tenantDomainName", callbackData.TenantDomainName),
new("tenantCustomDomain", callbackData.TenantCustomDomain ?? string.Empty),
new("userId", userinfo.TryGetValue("sub", out var userId) ? userId.GetString() : string.Empty),
new("tenantId", userinfo.TryGetValue("tnt_id", out var tenantId) ? tenantId.GetString() : string.Empty),
};
/* ***** BEGIN NEW CSRF LOGIC ***** */
// Generate the CSRF secret.
var csrfSecret = CsrfUtils.GenerateCsrfSecret();
// Initialize the auth session cookie, which now includes the CSRF secret.
await context.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)),
new AuthenticationProperties { IsPersistent = true });
// Create the CSRF token cookie.
CsrfUtils.UpdateCsrfTokenCookie(httpContext, csrfSecret);
/* ***** END NEW CSRF LOGIC ***** */
var tenantPostLoginRedirectUrl = $"http://{callbackResult.CallbackData.TenantDomainName}.example.com";
return Results.Redirect(tenantPostLoginRedirectUrl);
} catch (Exception ex)
{
return Results.Problem(detail: $"Unexpected error: {ex.Message}", statusCode: 500);
}
})
...
Logout Endpoint
The Logout endpoint should destroy the CSRF cookie before redirecting to the Wristband Logout endpoint.
// AuthRoutes.cs
...
app.MapGet("/auth/logout", async (HttpContext httpContext, IWristbandAuthService wristbandAuth) =>
{
try
{
/* ***** BEGIN NEW CSRF LOGIC ***** */
httpContext.Response.Cookies.Delete("XSRF-TOKEN");
/* ***** END NEW CSRF LOGIC ***** */
var refreshToken = context.User.FindFirst("refreshToken")?.Value ?? string.Empty;
var tenantCustomDomain = context.User.FindFirst("tenantCustomDomain")?.Value ?? string.Empty;
var tenantDomainName = context.User.FindFirst("tenantDomainName")?.Value ?? string.Empty;
var logoutConfig = new LogoutConfig
{
RefreshToken = refreshToken ?? null,
TenantCustomDomain = tenantCustomDomain ?? null,
TenantDomainName = tenantDomainName ?? null,
};
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var wristbandLogoutUrl = await wristbandAuth.Logout(httpContext, logoutConfig);
return Results.Redirect(wristbandLogoutUrl);
}
catch (Exception ex)
{
return Results.Problem(detail: $"Unexpected error: {ex.Message}", statusCode: 500);
}
});
...
Updated 8 days ago
What’s Next
Lastly, let's enhance the frontend to be able to pass custom CSRF request headers to C#.