This episode covers web security in Fiber v3: the csrf middleware with KeyLookup and cookie configuration, the cors middleware with AllowOrigins and credentials, and combined practices for protecting APIs from cross-site attacks.

The web has threats inherent to how it works: other sites can exploit a user's browser to send unwanted requests. Episode 18 covers two middleware that protect against these threats — CSRF and CORS — and how to combine them.
CSRF prevents malicious sites from forcing the browser to send harmful requests to your application; CORS controls which sites are allowed to read responses. Both are essential to understand when building APIs consumed by browsers.
csrf.New() protects state mutations (POST, PUT, DELETE) with a per-session token:
app.Use(csrf.New())Without configuration, the middleware creates a CSRF token, stores it in a cookie, and validates subsequent requests. If the request carries a matching token, it passes; if not, an error is returned. Tokens are generated randomly per visitor and must be sent again on every mutation.
By default the token is read from the X-CSRF-Token header. Change the token source with KeyLookup:
app.Use(csrf.New(csrf.Config{
KeyLookup: "header:X-CSRF-Token",
ErrorHandler: func(c fiber.Ctx, err error) error {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "token CSRF tidak valid",
})
},
}))KeyLookup: "header:X-CSRF-Token" means the token is expected in that header. The source:key format also supports cookie:csrf or form:csrf. ErrorHandler changes the default response to JSON — important for consistency with your API.
The cookie that stores the token also needs secure settings:
app.Use(csrf.New(csrf.Config{
CookieName: "csrf_token",
CookieHTTPOnly: true,
CookieSameSite: "Lax",
}))CookieHTTPOnly: true prevents JavaScript from reading the token via document.cookie — reducing theft risk. CookieSameSite: "Lax" blocks cookie delivery on cross-site requests, closing the main CSRF vector. Enable CookieSecure in production so the cookie is only sent over HTTPS.
CORS controls which sites may read responses:
app.Use(cors.New())By default, all origins are allowed (AllowOrigins: "*"). This isn't safe for APIs holding sensitive data — every site could read the responses. For public APIs without cookie authentication, this default is sometimes acceptable; for other APIs, restrict origins explicitly.
For production, register the allowed origins explicitly:
app.Use(cors.New(cors.Config{
AllowOrigins: "https://gofiber.io, https://gofiber.net",
AllowHeaders: "Origin, Content-Type, Accept, Authorization",
AllowMethods: "GET, POST, PUT, DELETE, OPTIONS",
}))AllowOrigins accepts a comma-separated list of origins; anything else is answered without CORS headers. AllowHeaders and AllowMethods announce what may be sent. If clients use cookies with CORS, add AllowCredentials: true — and remember, AllowCredentials must not be used with a wildcard origin *.
For a dynamic origin list — for example from a database — use AllowOriginsFunc:
app.Use(cors.New(cors.Config{
AllowOriginsFunc: func(origin string) bool {
return strings.HasSuffix(origin, ".myapp.example.com")
},
}))AllowOriginsFunc returns true for every matching origin. This example allows all subdomains of .myapp.example.com without registering them one by one — useful when tenants create new subdomains dynamically.
The installation order of middleware determines behavior. A recommended convention:
app.Use(recover.New())
app.Use(cors.New(cors.Config{AllowOrigins: "https://app.example.com"}))
app.Use(csrf.New())
api := app.Group("/api", jwtware.New(jwtware.Config{
SigningKey: jwtware.SigningKey{Key: []byte(secret)},
}))recover goes outermost so a panic doesn't kill the server; cors sets headers for all responses; csrf protects mutations; and JWT guards the /api area. CSRF is usually unnecessary for purely token-based APIs — choose by need: cookie-based sessions use CSRF, token-based use JWT.
curl -i http://localhost:3000/
curl -i -H "Origin: https://evil.example.com" http://localhost:3000/api/dataThe first request shows the CSRF header in the response. The second request from a foreign origin doesn't receive the Access-Control-Allow-Origin header — the browser will block reading the response. Try with a registered origin and compare the results.
Key takeaways:
csrf.New() protects mutations with a per-session token; by default the token is in the X-CSRF-Token header.KeyLookup sets the token source: header:, cookie:, or form:.CookieHTTPOnly, CookieSameSite, and CookieSecure.cors.New() allows all origins by default — restrict with AllowOrigins for production.AllowCredentials: true must not be combined with a wildcard origin.AllowOriginsFunc suits dynamic origin lists.In the next episode, episode 19, we discuss sessions and authentication — the session middleware with Redis/Memcache/MySQL storage, storing session data, and a complete authentication flow.