CSRF
The CSRF middleware protects against Cross-Site Request Forgery attacks by validating tokens on unsafe HTTP methods such as POST, PUT, and DELETE. It responds with 403 Forbidden when validation fails. Safe methods (GET, HEAD, OPTIONS, TRACE, QUERY) are not validated; note that QUERY is classified as safe per RFC 10008, so do not perform state changes in QUERY handlers. For protection without tokens, see Cross-Origin Protection Without Tokens.
Table of Contentsβ
- Quick Start
- Best Practices & Production Requirements
- Configuration by Application Type
- Recipes for Common Use Cases
- Using CSRF Tokens
- Security Model
- Token Extractors
- Advanced Configuration
- API Reference
- Config Properties
- Error Types
- Constants
Quick Startβ
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/extractors"
"github.com/gofiber/fiber/v3/middleware/csrf"
)
// Default config (development only)
app.Use(csrf.New())
// Production config
app.Use(csrf.New(csrf.Config{
CookieName: "__Host-csrf_",
CookieSecure: true,
CookieHTTPOnly: true, // false for SPAs
CookieSameSite: "Lax",
CookieSessionOnly: true,
Extractor: extractors.FromHeader("X-Csrf-Token"),
Session: sessionStore,
// Redaction is enabled by default. Set DisableValueRedaction when you must expose tokens or storage keys in diagnostics.
// DisableValueRedaction: true,
}))
Best Practices & Production Requirementsβ
CookieSecure: true(HTTPS only)CookieSameSite: "Lax"or"Strict"- Use
Sessionstore for better security
- Always use HTTPS in production
- Use sessions for authenticated applications
- Set
CookieSecure: trueand appropriate SameSite values - Implement XSS protection alongside CSRF
- Regenerate tokens after auth changes
- Use
__Host-cookie prefix when possible
To mitigate BREACH attacks, ensure your pages are served over HTTPS, disable HTTP compression, and implement rate limiting for requests. The CSRF token is sent as a header on every request, so if you include the token in a page that is vulnerable to BREACH, an attacker may be able to extract the token.
Configuration by Application Typeβ
Server-Side Rendered Appsβ
app.Use(csrf.New(csrf.Config{
CookieName: "__Host-csrf_",
CookieSecure: true,
CookieHTTPOnly: true, // Secure - blocks JavaScript
CookieSameSite: "Lax",
CookieSessionOnly: true,
Extractor: extractors.FromForm("_csrf"),
Session: sessionStore,
}))
Single Page Applications (SPAs)β
app.Use(csrf.New(csrf.Config{
CookieName: "__Host-csrf_",
CookieSecure: true,
CookieHTTPOnly: false, // Required for JavaScript access to tokens
CookieSameSite: "Lax",
CookieSessionOnly: true,
Extractor: extractors.FromHeader("X-Csrf-Token"),
Session: sessionStore,
}))
SPAs require CookieHTTPOnly: false to access tokens via JavaScript. This slightly increases XSS risk but is necessary for SPA functionality.
Cross-Origin Protection Without Tokensβ
csrf.NewCrossOriginProtection is a separate, tokenless middleware. It rejects unsafe cross-origin browser requests by their Sec-Fetch-Site and Origin headers, following Go's net/http.CrossOriginProtection. It sets no cookie, creates no token, and needs no storage or session, so the client has nothing to send back. csrf.TokenFromContext returns an empty string and csrf.HandlerFromContext returns nil, since this middleware creates neither.
app.Use(csrf.NewCrossOriginProtection(csrf.CrossOriginConfig{
TrustedOrigins: []string{"https://admin.example.com"},
// Exempt a route that must accept cross-origin posts, such as an SSO callback
Next: func(c fiber.Ctx) bool {
return c.Path() == "/sso/callback"
},
}))
A request is allowed when one of these holds:
- The method is
GET,HEAD, orOPTIONS. Sec-Fetch-Siteissame-originornone.Sec-Fetch-SiteandOriginare both absent. Such a request is same-origin or does not come from a browser.Sec-Fetch-Siteis absent and the host ofOriginequals theHostheader.Originis listed inTrustedOrigins.
Every other request is passed to ErrorHandler with ErrCrossOriginRequest, which answers 403 Forbidden by default. That includes Sec-Fetch-Site: same-site: a page on a sibling subdomain, such as app.example.com calling api.example.com, is cross-origin here, so list its origin in TrustedOrigins.
Requests that carry neither header are allowed, so this middleware does not replace authentication or authorization. Do not perform state changes in GET, HEAD, or OPTIONS handlers.
With csrf.New, a request from a trusted origin still needs a valid token. Here a match is the entire check. A wildcard such as https://*.example.com extends it to every subdomain, including one that serves user content or one an attacker takes over through a dangling DNS record. Prefer exact origins.
Differences from net/http.CrossOriginProtection:
TrustedOriginstakes the same values as incsrf.Config: entries are normalized (case, trailing slash, IPv6 spelling) but a default port is kept as written and matched exactly, subdomain wildcards are supported, and onlyhttpandhttpsorigins are accepted. An entry such ascapacitor://localhostpanics at startup.- There is no
AddInsecureBypassPattern. UseNextto exempt routes. - A request that repeats the
Sec-Fetch-Siteheader, or theOriginheader where the decision depends on it, is denied. - Rule 4 never applies to a request without a
Host.
Shared with net/http, but different from csrf.New:
TRACEandQUERYare checked like any other unsafe method.Sec-Fetch-SiteandOriginare compared as sent. Browsers send both in lower case, soSame-Originorhttps://EXAMPLE.comis denied.- Rule 4 compares hosts only. It ignores the scheme and does not fold default ports, so
Origin: http://example.compasses on an HTTPS request toexample.com. Current browsers sendSec-Fetch-Siteon HTTPS requests and never get to rule 4 there; for older ones, enable HSTS, for example with the Helmet middleware. - Rule 4 reads the
Hostheader as sent.X-Forwarded-Hostis not consulted, even behind a trusted proxy. If your proxy rewritesHost, add your public origin toTrustedOrigins. - The
Refererheader is not consulted.
Recipes for Common Use Casesβ
- Without Sessions: CSRF Recipe - Simple Double Submit Cookie pattern
- With Sessions: CSRF with Session Recipe - More secure Synchronizer Token pattern
Using CSRF Tokensβ
Server-Side Formsβ
func formHandler(c fiber.Ctx) error {
token := csrf.TokenFromContext(c)
return c.SendString(fmt.Sprintf(`
<form method="POST" action="/submit">
<input type="hidden" name="_csrf" value="%s">
<input type="text" name="message" required>
<button type="submit">Submit</button>
</form>
`, token))
}
Single Page Applicationsβ
func apiHandler(c fiber.Ctx) error {
token := csrf.TokenFromContext(c)
return c.JSON(fiber.Map{
"csrf_token": token,
"data": "your data",
})
}
// Get CSRF token from cookie
function getCsrfToken() {
const value = `; ${document.cookie}`;
const parts = value.split(`; __Host-csrf_=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// Use with fetch API
async function makeRequest(url, data) {
const csrfToken = getCsrfToken();
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Csrf-Token': csrfToken
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
Security Modelβ
The middleware employs a robust, defense-in-depth strategy to protect against CSRF attacks. The primary defense is token-based validation, which operates in one of two modes depending on your configuration. This is supplemented by a mandatory secondary check on the request's origin. This section describes csrf.New; csrf.NewCrossOriginProtection validates neither tokens nor the Referer header, see Cross-Origin Protection Without Tokens.
Fetch Metadata Guardrailsβ
- Sec-Fetch-Site: For unsafe methods, the middleware inspects the
Sec-Fetch-Siteheader when present. If the header value (compared case-insensitively, ignoring surrounding spaces) is not one of "same-origin", "none", "same-site", or "cross-site", the request is rejected withErrFetchSiteInvalid. If the header is valid or absent, the request proceeds to the standard origin and token validation checks. This provides an early check to block requests with invalidSec-Fetch-Sitevalues, while allowing legitimate same-site and cross-site requests to be validated by the existing mechanisms.
1. Token Validation Patternsβ
Double Submit Cookie (Default Mode)β
This is the default pattern, used when a Session store is not configured. It is a "semi-stateless" approach; while it doesn't tie tokens to a specific user session, the server still maintains a record of all validly issued tokens.
-
How it Works:
- On a user's first visit (or a safe request like
GET), the middleware generates a unique token. - This token is sent to the client in a
Set-Cookieheader. - The server also stores this token (in memory by default or in the configured
Storage). It confirms the token is server-generated and still valid, but it is not tied to a specific user. - For subsequent unsafe requests (e.g.,
POST,PUT), the client must read the token from the cookie and echo it in a different location, such as theX-Csrf-Tokenheader.
- On a user's first visit (or a safe request like
-
Validation: The middleware validates three things: that the token from the header/form exactly matches the token from the cookie, that the token exists in the server-side storage, and that it has not expired.
-
Why it is secure: Attackers on a malicious domain cannot read the victim's cookie to forge a matching header. They also cannot invent a token because it wouldn't exist in the server's storage registry.
Synchronizer Token (Session-Based Mode)β
This is a more secure, stateful pattern that is automatically enabled when you provide a Session store in the configuration.
-
How it Works:
- A unique token is generated and stored directly within the user's session data on the server.
- The token is also sent to the client as a cookie.
- For unsafe requests, the client sends the token back in a header or form field.
-
Validation: The middleware performs a multi-step validation:
- It first performs the standard Double Submit Cookie check: the token from the header/form must exactly match the token from the cookie. This is a fast and efficient first line of defense, and there is little benefit of skipping it.
- It then validates that this token exists and is valid within the user's server-side session. This is the authoritative check that ties the token to the authenticated user.
-
Why it is more secure: Tying the token to the server-side session provides the strongest CSRF protection, as the token is then guaranteed to have been generated for the specific user. While browsers automatically send the required cookie, custom API clients must remember to include the cookie with their requests for validation to succeed.
// Enable the more secure Synchronizer Token pattern
app.Use(csrf.New(csrf.Config{
Session: sessionStore, // Providing a session store activates this mode
}))
2. Origin & Referer Validationβ
As a crucial second layer of defense, the middleware checks the origin of every unsafe request before it looks at the token.
- If the request has an
Originheader other thannull, its scheme and host must match the scheme and host of the request or be explicitly allowed in theTrustedOriginslist. Default ports are treated as equal only in the comparison with the request (example.comandexample.com:443match on HTTPS). An IPv6 host matches in any spelling, in the comparison with the request and inTrustedOriginsalike:http://[::1]:3000matchesHost: [0:0:0:0:0:0:0:1]:3000and the entryhttp://[0:0:0:0:0:0:0:1]:3000. ATrustedOriginsentry is matched as written:https://a.exampledoes not matchOrigin: https://a.example:443, andhttps://a.example:443does not match anOriginwithout the port. Browsers sendOriginwithout a default port, so write entries without one. This applies to HTTP and HTTPS requests alike. - If
Originis absent ornulland the connection is HTTPS, theRefererheader takes its place: it must be present and match in the same way. Over plain HTTP, a request with noOrigin(orOrigin: null) has nothing to compare and goes on to token validation. - A repeated
OriginorSec-Fetch-Siteheader, and a repeatedRefererheader when it is consulted, is rejected instead of being resolved to one of its lines. - The scheme and host of the request come from
c.Scheme()andc.Host(). Behind a reverse proxy,X-Forwarded-ProtoandX-Forwarded-Hostare used only for requests from a proxy thatTrustProxyandTrustProxyConfigtrust. - This check is performed in addition to token validation and provides strong protection because these headers are reliably set by browsers and cannot be programmatically controlled by an attacker from a malicious site.
Token Extractorsβ
This middleware uses the shared extractors package for token extraction. For full details on extractor types, chaining, security, and advanced usage, see the Extractors Guide.
Extractor Source Constants:
Extractor source constants (such as SourceHeader, SourceForm, etc.) are defined in the shared extractors package, not in the CSRF middleware itself. Refer to the Extractors Guide for their definitions and usage.
CSRF-Specific Extractor Notesβ
For CSRF protection, prefer secure extraction methods:
- Headers (
extractors.FromHeader("X-Csrf-Token")) β Most secure, not logged in URLs - Form data (
extractors.FromForm("_csrf")) β Secure for form submissions. It also accepts a value in the query string (see Extractor Behavior), so submit the token in the form body - Avoid URL parameters β Query/param extractors expose tokens in logs and browser history
Cookies are generally not a secure source for CSRF tokens. The middleware will panic if you configure an extractor that reads from cookies with the same name as your CSRF cookie. This is because reading the CSRF token from a cookie with the same name as the CSRF cookie defeats CSRF protection entirely, as the extracted token will always match the cookie value, allowing any CSRF attack to succeed.
Advanced usage: In rare cases, you may securely extract a CSRF token from a cookie if:
- You read from a different cookie (not the CSRF cookie itself)
- You use multiple cookies for custom validation
- You implement custom logic across different cookie sources
If you do this, set the extractorβs Source to SourceCookie and use a cookie name that differs from your CSRF cookie. The middleware accepts a different cookie and panics only when an extractor with Source set to SourceCookie has a Key equal to the CSRF cookie name. A name that differs only in letter case is accepted too, with a logged warning.
Warning: Cookie-based extraction is strongly discouraged, as it is easy to misconfigure and creates security risks. Prefer extracting tokens from headers or form fields for robust CSRF protection. See the Extractors Guide for more details.
Route-Specific Configurationβ
You can configure different extraction methods for different routes:
// API routes - header extraction for AJAX/fetch requests
api := app.Group("/api")
api.Use(csrf.New(csrf.Config{
Extractor: extractors.FromHeader("X-Csrf-Token"),
}))
// Form routes - form field extraction for traditional forms
forms := app.Group("/forms")
forms.Use(csrf.New(csrf.Config{
Extractor: extractors.FromForm("_csrf"),
}))
Custom CSRF Extractorsβ
For specialized CSRF token extraction needs, you can create custom extractors. See the Extractors Guide for advanced patterns and security notes.
NEVER create custom extractors that read from cookies using the same CookieName as your CSRF configuration. This completely defeats CSRF protection by making the extracted token always match the cookie value, allowing any CSRF attack to succeed.
// β NEVER DO THIS - Completely defeats CSRF protection
badExtractor := extractors.Extractor{
Extract: func(c fiber.Ctx) (string, error) {
return c.Cookies("csrf_"), nil // Always passes validation!
},
Source: extractors.SourceCustom,
Key: "csrf_",
}
// β
DO THIS - Extract from different source than cookie
app.Use(csrf.New(csrf.Config{
CookieName: "csrf_",
Extractor: extractors.FromHeader("X-Csrf-Token"), // Header vs cookie comparison
}))
The middleware uses the Double Submit Cookie pattern β it compares the extracted token against the cookie value. If you configure an extractor that reads from the same cookie, it will panic because they will always match and provide zero CSRF protection.
The panic is based on the extractor's declared Source and Key, so it only catches an extractor with Source set to extractors.SourceCookie and Key equal to the CSRF cookie name, such as extractors.FromCookie("csrf_"), also when it is nested in a Chain. The middleware cannot look inside a custom Extract function, so badExtractor above is accepted without a panic and offers no protection.
Bearer Token Embedding & Custom Extractorsβ
You can create advanced extractors for use cases like JWT embedding or JSON body parsing. See the Extractors Guide for secure implementation patterns and more examples.
Fallback Extractionβ
For applications that need to support both AJAX and form submissions:
// Try header first (AJAX), fall back to form (traditional forms)
app.Use(csrf.New(csrf.Config{
Extractor: extractors.Chain(
extractors.FromHeader("X-Csrf-Token"),
extractors.FromForm("_csrf"),
),
}))
Chaining extractors increases complexity. Use only when you need to support multiple client types. See the Extractors Guide for details and security notes.
Advanced Configurationβ
Trusted Originsβ
app.Use(csrf.New(csrf.Config{
TrustedOrigins: []string{
"https://trusted.example.com",
"https://*.example.com", // Wildcard subdomains
},
}))
Custom Error Handlerβ
app.Use(csrf.New(csrf.Config{
ErrorHandler: func(c fiber.Ctx, err error) error {
accepts := c.Accepts("html", "json")
path := c.Path()
if accepts == "json" || strings.HasPrefix(path, "/api/") {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "Forbidden",
})
}
return c.Status(fiber.StatusForbidden).Render("error", fiber.Map{
"Title": "Forbidden",
"Status": fiber.StatusForbidden,
}, "layouts/main")
},
}))
Custom Storage/Databaseβ
You can use any storage from our storage package.
storage := sqlite3.New() // From github.com/gofiber/storage/sqlite3/v2
app.Use(csrf.New(csrf.Config{
Storage: storage,
}))
Token Managementβ
// Delete token (e.g., on logout)
handler := csrf.HandlerFromContext(c)
if handler != nil {
if err := handler.DeleteToken(c); err != nil {
// handle error, e.g. log it
}
}
// With session middleware
// Destroying the session will also remove the CSRF token if using session-based CSRF.
if s := session.FromContext(c); s != nil {
if err := s.Destroy(); err != nil {
// handle error, e.g. log it
}
}
API Referenceβ
// Create middleware
func New(config ...csrf.Config) fiber.Handler
// Create tokenless middleware
func NewCrossOriginProtection(config ...csrf.CrossOriginConfig) fiber.Handler
// Get token from context
func TokenFromContext(ctx any) string
// Get handler from context
func HandlerFromContext(ctx any) *csrf.Handler
// Delete token
func (h *csrf.Handler) DeleteToken(c fiber.Ctx) error
TokenFromContext and HandlerFromContext accept a fiber.CustomCtx, fiber.Ctx, a *fasthttp.RequestCtx, or a context.Context.
Config Propertiesβ
| Property | Type | Description | Default |
|---|---|---|---|
| Next | func(fiber.Ctx) bool | Skip middleware when returns true | nil |
| CookieName | string | CSRF cookie name | "csrf_" |
| CookieDomain | string | CSRF cookie domain | "" |
| CookiePath | string | CSRF cookie path | "" |
| CookieSecure | bool | HTTPS only cookie (required for production) | false |
| CookieHTTPOnly | bool | Prevent JavaScript access (use false for SPAs) | false |
| CookieSameSite | string | SameSite attribute (use "Lax" or "Strict") | "Lax" |
| CookieSessionOnly | bool | Session-only cookie (expires on browser close) | false |
| IdleTimeout | time.Duration | Token expiration time, counted from its last use. In session mode behind the session middleware, a token is renewed only once less than half of it is left, so there it stays valid for at least half of IdleTimeout and at most IdleTimeout after its last use (set twice the idle window you want), and the cookie expires with it; a request that finds its token fresh does not count as a change of the session, so its save cannot override a change another request stored meanwhile, while the session middleware still writes the session back on every request to slide its idle timeout. Without the session middleware in front, the token is renewed and the session saved on every request. | 30 * time.Minute |
| KeyGenerator | func() string | Token generation function | utils.SecureToken |
| ErrorHandler | fiber.ErrorHandler | Custom error handler | defaultErrorHandler |
| Extractor | extractors.Extractor | Token extraction method with metadata | extractors.FromHeader("X-Csrf-Token") |
| DisableValueRedaction | bool | Disables redaction of tokens and storage keys in logs and error messages. | false |
| Session | *session.Store | Session store (recommended for production) | nil |
| Storage | fiber.Storage | Token storage (ignored if Session is set). A token is stored under csrf:<token>, so the storage can be shared with other middleware. Tokens stored by an earlier version are not found after the upgrade: the next safe request gets a new token, and the old entries expire on their own. | In-memory store |
| TrustedOrigins | []string | Trusted origins for cross-origin requests. Entries must be http or https origins without a path, such as https://app.example.com. https://*.example.com matches subdomains at any depth, but not example.com itself. An entry is matched as written, so a default port (https://app.example.com:443) matches only an Origin that carries it. An invalid entry panics at startup. | [] |
| SingleUseToken | bool | Generate new token after each use | false |
Default Configβ
var ConfigDefault = Config{
CookieName: "csrf_",
CookieSameSite: "Lax",
IdleTimeout: 30 * time.Minute,
KeyGenerator: utils.SecureToken,
ErrorHandler: defaultErrorHandler, // returns fiber.ErrForbidden
Extractor: extractors.FromHeader(HeaderName),
DisableValueRedaction: false,
}
When you pass a Config, empty or zero values for CookieName, CookieSameSite, IdleTimeout, KeyGenerator, ErrorHandler, and Extractor are replaced with these defaults.
CrossOriginConfig Propertiesβ
| Property | Type | Description | Default |
|---|---|---|---|
| Next | func(fiber.Ctx) bool | Skip middleware when returns true | nil |
| ErrorHandler | fiber.ErrorHandler | Custom error handler | defaultErrorHandler |
| TrustedOrigins | []string | Origins allowed to send unsafe cross-origin requests | [] |
Error Typesβ
var (
ErrTokenNotFound = errors.New("csrf: token not found")
ErrTokenInvalid = errors.New("csrf: token invalid")
ErrFetchSiteInvalid = errors.New("csrf: sec-fetch-site header invalid")
ErrRefererNotFound = errors.New("csrf: referer header missing")
ErrRefererInvalid = errors.New("csrf: referer header invalid")
ErrRefererNoMatch = errors.New("csrf: referer does not match host or trusted origins")
ErrOriginInvalid = errors.New("csrf: origin header invalid")
ErrOriginNoMatch = errors.New("csrf: origin does not match host or trusted origins")
)
// Returned by NewCrossOriginProtection
var ErrCrossOriginRequest = errors.New("csrf: cross-origin request denied")
Constantsβ
const (
HeaderName = "X-Csrf-Token"
)