{"id":1800,"date":"2025-05-11T07:50:18","date_gmt":"2025-05-11T07:50:18","guid":{"rendered":"https:\/\/shreyapohekar.com\/blogs\/?p=1800"},"modified":"2025-05-14T05:40:28","modified_gmt":"2025-05-14T05:40:28","slug":"how-servers-handle-csrf-tokens-generation-validation-and-best-practices","status":"publish","type":"post","link":"https:\/\/shreyapohekar.com\/blogs\/how-servers-handle-csrf-tokens-generation-validation-and-best-practices\/","title":{"rendered":"How Servers Handle CSRF Tokens: Generation, Validation, and Best Practices"},"content":{"rendered":"\n<p>Welcome to Part 2 of the CSRF series!<br>While spotting CSRF vulnerabilities during testing or bug bounties is often straightforward, have you ever paused to think about what really happens behind the scenes when implementing mitigations?<\/p>\n\n\n\n<p>In <a href=\"https:\/\/shreyapohekar.com\/blogs\/csrf-why-put-requests-are-safer-and-how-modern-browsers-prevent-csrf-attacks\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Part 1<\/a>, we explored the fundamentals of Cross-Site Request Forgery (CSRF), why it&#8217;s dangerous, and how browsers now defend against it using mechanisms like SameSite cookies and CORS. But browser-level protections are not always enough. To fully secure your application, especially for POST-based form submissions and API endpoints \u2014 you need to implement <strong>CSRF tokens<\/strong>.<\/p>\n\n\n\n<p>In this part, we\u2019ll dive deep into:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>What CSRF tokens are<\/li>\n\n\n\n<li>How they\u2019re generated and validated<\/li>\n\n\n\n<li>Why they must be generated server-side<\/li>\n\n\n\n<li>Where to store them safely<\/li>\n\n\n\n<li>How to eliminate the need for database lookups using HMAC or encrypted token techniques<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">What Are CSRF Tokens?<\/h3>\n\n\n\n<p>A <strong>CSRF token<\/strong> is a unique, unpredictable, and secure value generated by the server and sent to the client. When the client submits a request (especially modifying ones like POST), it must include that token. The server then verifies the token before executing the request.<\/p>\n\n\n\n<p>This mechanism ensures that the request originated from a legitimate user interaction on the actual website \u2014 not from an attacker-controlled third-party page.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Why tokens are generated Server-Side?<\/h4>\n\n\n\n<p>The server \u2014 not the client \u2014 must generate the CSRF token. This is because:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The <strong>server is the trusted source<\/strong> in the communication.<\/li>\n\n\n\n<li>The token must be <strong>bound to the user\u2019s session<\/strong>, ensuring that it\u2019s unique per user.<\/li>\n\n\n\n<li>Only the server can validate that the incoming token is authentic.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Step-by-Step CSRF Token Flow<\/h3>\n\n\n\n<p>Let\u2019s walk through a typical flow of how CSRF tokens work in a secure web application.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1. <strong>Token Generation<\/strong><\/h4>\n\n\n\n<p>When a user visits a page with a form or API endpoint:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The server generates a <strong>random CSRF token<\/strong> \u2014 often a UUID or cryptographically secure string.<\/li>\n\n\n\n<li>This token is stored <strong>server-side<\/strong><\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">2. <strong>Token Distribution<\/strong><\/h4>\n\n\n\n<p>The CSRF token is sent to the client in one of two safe ways:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Embedded in an HTML form<\/strong> as a hidden field:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;input type=\"hidden\" name=\"csrf_token\" value=\"abc123xyz\"&gt;\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Exposed via JavaScript<\/strong> for AJAX requests, usually through a templating engine or meta tag:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;meta name=\"csrf-token\" content=\"abc123xyz\"&gt;\n<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">3. <strong>Client Submits Request<\/strong><\/h4>\n\n\n\n<p>When the user submits the form or makes an API call:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The CSRF token is included in the request body (for forms) or in a custom HTTP header (for AJAX):<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>POST \/update-profile HTTP\/1.1\nX-CSRF-Token: abc123xyz\n<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">4. <strong>Server Validates the Token<\/strong><\/h4>\n\n\n\n<p>The server compares the submitted token with the one it issued to the session:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>If it matches \u2192  The request is considered valid.<\/li>\n\n\n\n<li>If it doesn&#8217;t match or is missing \u2192  The request is blocked with a 403 Forbidden response.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Where Should You Store the Token?<\/h3>\n\n\n\n<p>To be effective and safe from tampering, CSRF tokens <strong>must not be stored in insecure or easily accessible places<\/strong> like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>localStorage<\/code> (accessible via JavaScript and vulnerable to XSS)<\/li>\n\n\n\n<li>Query strings (visible in URLs and logs)<\/li>\n<\/ul>\n\n\n\n<p>Instead, use:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Hidden form fields<\/strong> (safe from cross-site reads)<\/li>\n\n\n\n<li><strong>Secure cookies<\/strong> (preferably <code>HttpOnly<\/code> and <code>SameSite<\/code>)<\/li>\n\n\n\n<li><strong>Custom HTTP headers<\/strong> for API requests (e.g., <code>X-CSRF-Token<\/code>)<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">How to Avoid Database Lookups: Stateless CSRF Tokens<\/h3>\n\n\n\n<p>A common challenge with CSRF tokens is storing them per session, especially in high-scale or stateless environments where storing session data in a database can be expensive or undesirable.<\/p>\n\n\n\n<p>To solve this, you can use <strong>stateless, self-validating tokens<\/strong> generated with HMAC (Hash-based Message Authentication Code) or encryption.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Stateless CSRF Tokens with HMAC<\/h3>\n\n\n\n<p>These tokens include all the information needed for validation, so no server-side lookup is required. This can be attributed to how a jwt works.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Structure of the Token:<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>csrf_token = base64(user_id + timestamp + HMAC(user_id + timestamp, secret_key))\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>user_id<\/code>: Tied to the session or authentication state<\/li>\n\n\n\n<li><code>timestamp<\/code>: Used for token expiration<\/li>\n\n\n\n<li><code>HMAC(...)<\/code>: A secure signature to ensure the token hasn&#8217;t been tampered with<\/li>\n\n\n\n<li><code>secret_key<\/code>: Stored securely on the server<\/li>\n<\/ul>\n\n\n\n<p><strong>Example:<\/strong><\/p>\n\n\n\n<p>Let\u2019s say:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>user_id = 789<\/code><\/li>\n\n\n\n<li><code>timestamp = 1715000000<\/code><\/li>\n\n\n\n<li><code>secret_key = myapp_secret<\/code><\/li>\n<\/ul>\n\n\n\n<p>The HMAC would be:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HMAC(\"7891715000000\", \"myapp_secret\")\n<\/code><\/pre>\n\n\n\n<p>Then, concatenate and base64-encode the whole token.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Stateless Token Validation Flow<\/h3>\n\n\n\n<p>When the server receives a request with a CSRF token, It does the following.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Decode the token<\/strong><\/li>\n\n\n\n<li><strong>Extract the user_id and timestamp<\/strong><\/li>\n\n\n\n<li><strong>Recompute the HMAC<\/strong> using the same algorithm and secret key<\/li>\n\n\n\n<li><strong>Compare the computed HMAC<\/strong> with the one provided in the token<\/li>\n\n\n\n<li><strong>Check the timestamp<\/strong> to ensure the token is within an acceptable time window (e.g., 30 minutes)<\/li>\n<\/ol>\n\n\n\n<p>If all checks pass, the token is valid.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Advantages of Stateless Tokens<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>No need for session storage or database lookups<\/li>\n\n\n\n<li>Highly scalable for distributed systems or APIs<\/li>\n\n\n\n<li>Secure, provided the secret key remains confidential<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">CSRF Token Expiration<\/h3>\n\n\n\n<p>To add another layer of protection, always include a <strong>timestamp<\/strong> in the token and reject tokens older than a certain threshold.<\/p>\n\n\n\n<p>This ensures:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Stolen tokens are only valid for a limited time<\/li>\n\n\n\n<li>Replay attacks are limited in scope<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Summary: Best Practices for CSRF Tokens<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Best Practice<\/th><th>Why It Matters<\/th><\/tr><\/thead><tbody><tr><td><strong>Generate tokens server-side<\/strong><\/td><td>Ensures trust and integrity<\/td><\/tr><tr><td><strong>Use strong randomness or HMAC<\/strong><\/td><td>Prevents guessing and forgery<\/td><\/tr><tr><td><strong>Tie token to session\/user<\/strong><\/td><td>Prevents token reuse across accounts<\/td><\/tr><tr><td><strong>Embed in forms or send via headers<\/strong><\/td><td>Allows proper validation<\/td><\/tr><tr><td><strong>Use stateless HMAC tokens when scaling<\/strong><\/td><td>Avoids performance hits from DB lookups<\/td><\/tr><tr><td><strong>Enforce token expiration<\/strong><\/td><td>Limits risk of token theft<\/td><\/tr><tr><td><strong>Do not store tokens in localStorage<\/strong><\/td><td>Prevents XSS-based leakage<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>By combining <strong>browser-level defenses<\/strong> (discussed in <a href=\"https:\/\/shreyapohekar.com\/blogs\/csrf-why-put-requests-are-safer-and-how-modern-browsers-prevent-csrf-attacks\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Part 1<\/a>) with <strong>robust token-based validation<\/strong>, your application can be effectively protected from CSRF attacks. Whether you\u2019re building a monolith or a stateless microservice architecture, implementing CSRF mitigation is a non-negotiable step in web application security.<\/p>\n\n\n\n<p>I hope you enjoyed reading this blog. See you in the next one. Until then, Happy defending!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Welcome to Part 2 of the CSRF series!While spotting CSRF vulnerabilities during testing or bug bounties is often straightforward, have you ever paused to think about what really happens behind the scenes when implementing mitigations? In Part 1, we explored the fundamentals of Cross-Site Request Forgery (CSRF), why it&#8217;s dangerous, and how browsers now defend [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1812,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"","ocean_second_sidebar":"","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"default","ocean_display_header":"default","ocean_header_style":"","ocean_center_header_left_menu":"","ocean_custom_header_template":"","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":0,"ocean_custom_logo_tablet_max_width":0,"ocean_custom_logo_mobile_max_width":0,"ocean_custom_logo_max_height":0,"ocean_custom_logo_tablet_max_height":0,"ocean_custom_logo_mobile_max_height":0,"ocean_header_custom_menu":"","ocean_menu_typo_font_family":"","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":"","ocean_post_title_style":"","ocean_post_title_background_color":"","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":"","ocean_post_oembed":"","ocean_post_self_hosted_media":"","ocean_post_video_embed":"","ocean_link_format":"","ocean_link_format_target":"self","ocean_quote_format":"","ocean_quote_format_link":"post","ocean_gallery_link_images":"on","ocean_gallery_id":[],"footnotes":""},"categories":[439,2,440,257],"tags":[444,345,441,442,443],"class_list":["post-1800","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-csrf","category-information-security","category-mitigations","category-web-application","tag-browsers","tag-csrf","tag-defending","tag-mitigations","tag-put-request","entry","has-media"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1800"}],"collection":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/comments?post=1800"}],"version-history":[{"count":7,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1800\/revisions"}],"predecessor-version":[{"id":1817,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1800\/revisions\/1817"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/media\/1812"}],"wp:attachment":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/media?parent=1800"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/categories?post=1800"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/tags?post=1800"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}