Enhance Auth Endpoints To Manage CSRF Tokens
Update your Callback and Logout endpoints to create and clean up CSRF tokens.
Now that your CSRF middleware is set up to verify CSRF tokens, we need to update your Callback Endpoint and Logout Endpoint to create and clean up CSRF tokens.
Update Callback Endpoint
Your Callback Endpoint needs to be updated to create a CSRF token. The CSRF token will be derived from a session-specific CSRF secret. Once the CSRF token is generated, it will be stored in a cookie so that the frontend Javascript code can read it. In addition, the CSRF secret will be stored in the session cookie so the CSRF middleware can access it to verify the CSRF token.
// 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 callbackData = callbackResult.CallbackData;
var userinfo = callbackData.Userinfo;
var claims = new List<Claim>
{
new("accessToken", callbackData.AccessToken),
new("refreshToken", callbackData.RefreshToken ?? string.Empty),
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();
// Make sure to add the CSRF secret to the session claims.
claims.Add(new Claim("csrfSecret", csrfSecret));
// Initialize the auth session cookie, which now includes the CSRF secret.
await httpContext.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 appUrl = callbackResult.CallbackData.ReturnUrl ?? "http://localhost:3000/your-react-home-route";
return Results.Redirect(appUrl);
} catch (Exception ex)
{
return Results.Problem(detail: $"Unexpected error: {ex.Message}", statusCode: 500);
}
});
...
Update Logout Endpoint
Now, when a user logs out, we need to delete their 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 = httpContext.User.FindFirst("refreshToken")?.Value;
var tenantCustomDomain = httpContext.User.FindFirst("tenantCustomDomain")?.Value;
var tenantDomainName = httpContext.User.FindFirst("tenantDomainName")?.Value;
var logoutConfig = new LogoutConfig
{
RefreshToken = refreshToken,
TenantCustomDomain = tenantCustomDomain,
TenantDomainName = tenantDomainName,
};
await httpContext.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 7 days ago
Lastly, let's enhance the frontend to be able to pass a CSRF request header when making API calls to your backend.