mirror of
https://github.com/gofiber/fiber.git
synced 2025-05-30 03:12:39 +00:00
* feat!(middleware/session): re-write session middleware with handler * test(middleware/session): refactor to IdleTimeout * fix: lint errors * test: Save session after setting or deleting raw data in CSRF middleware * Update middleware/session/middleware.go Co-authored-by: Renan Bastos <renanbastos.tec@gmail.com> * fix: mutex and globals order * feat: Re-Add read lock to session Get method * feat: Migrate New() to return middleware * chore: Refactor session middleware to improve session handling * chore: Private get on store * chore: Update session middleware to use saveSession instead of save * chore: Update session middleware to use getSession instead of get * chore: Remove unused error handler in session middleware config * chore: Update session middleware to use NewWithStore in CSRF tests * test: add test * fix: destroyed session and GHSA-98j2-3j3p-fw2v * chore: Refactor session_test.go to use newStore() instead of New() * feat: Improve session middleware test coverage and error handling This commit improves the session middleware test coverage by adding assertions for the presence of the Set-Cookie header and the token value. It also enhances error handling by checking for the expected number of parts in the Set-Cookie header. * chore: fix lint issues * chore: Fix session middleware locking issue and improve error handling * test: improve middleware test coverage and error handling * test: Add idle timeout test case to session middleware test * feat: add GetSession(id string) (*Session, error) * chore: lint * docs: Update session middleware docs * docs: Security Note to examples * docs: Add recommendation for CSRF protection in session middleware * chore: markdown lint * docs: Update session middleware docs * docs: makrdown lint * test(middleware/session): Add unit tests for session config.go * test(middleware/session): Add unit tests for store.go * test(middleware/session): Add data.go unit tests * refactor(middleware/session): session tests and add session release test - Refactor session tests to improve readability and maintainability. - Add a new test case to ensure proper session release functionality. - Update session.md * refactor: session data locking in middleware/session/data.go * refactor(middleware/session): Add unit test for session middleware store * test: fix session_test.go and store_test.go unit tests * refactor(docs): Update session.md with v3 changes to Expiration * refactor(middleware/session): Improve data pool handling and locking * chore(middleware/session): TODO for Expiration field in session config * refactor(middleware/session): Improve session data pool handling and locking * refactor(middleware/session): Improve session data pool handling and locking * test(middleware/csrf): add session middleware coverage * chroe(middleware/session): TODO for unregistered session middleware * refactor(middleware/session): Update session middleware for v3 changes * refactor(middleware/session): Update session middleware for v3 changes * refactor(middleware/session): Update session middleware idle timeout - Update the default idle timeout for session middleware from 24 hours to 30 minutes. - Add a note in the session middleware documentation about the importance of the middleware order. * docws(middleware/session): Add note about IdleTimeout requiring save using legacy approach * refactor(middleware/session): Update session middleware idle timeout Update the idle timeout for the session middleware to 30 minutes. This ensures that the session expires after a period of inactivity. The previous value was 24 hours, which is too long for most use cases. This change improves the security and efficiency of the session management. * docs(middleware/session): Update session middleware idle timeout and configuration * test(middleware/session): Fix tests for updated panics * refactor(middleware/session): Update session middleware initialization and saving * refactor(middleware/session): Remove unnecessary comment about negative IdleTimeout value * refactor(middleware/session): Update session middleware make NewStore public * refactor(middleware/session): Update session middleware Set, Get, and Delete methods Refactor the Set, Get, and Delete methods in the session middleware to use more descriptive parameter names. Instead of using "middlewareContextKey", the methods now use "key" to represent the key of the session value. This improves the readability and clarity of the code. * feat(middleware/session): AbsoluteTimeout and key any * fix(middleware/session): locking issues and lint errors * chore(middleware/session): Regenerate code in data_msgp.go * refactor(middleware/session): rename GetSessionByID to GetByID This commit also includes changes to the session_test.go and store_test.go files to add test cases for the new GetByID method. * docs(middleware/session): AbsoluteTimeout * refactor(middleware/csrf): Rename Expiration to IdleTimeout * docs(whats-new): CSRF Rename Expiration to IdleTimeout and remove SessionKey field * refactor(middleware/session): Rename expirationKeyType to absExpirationKeyType and update related functions * refactor(middleware/session): rename Test_Session_Save_Absolute to Test_Session_Save_AbsoluteTimeout * chore(middleware/session): update as per PR comments * docs(middlware/session): fix indent lint * fix(middleware/session): Address EfeCtn Comments * refactor(middleware/session): Move bytesBuffer to it's own pool * test(middleware/session): add decodeSessionData error coverage * refactor(middleware/session): Update absolute timeout handling - Update absolute timeout handling in getSession function - Set absolute expiration time in getSession function - Delete expired session in GetByID function * refactor(session/middleware): fix *Session nil ctx when using Store.GetByID * refactor(middleware/session): Remove unnecessary line in session_test.go * fix(middleware/session): *Session lifecycle issues * docs(middleware/session): Update GetByID method documentation * docs(middleware/session): Update GetByID method documentation * docs(middleware/session): markdown lint * refactor(middleware/session): Simplify error handling in DefaultErrorHandler * fix( middleware/session/config.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * add ctx releases for the test cases --------- Co-authored-by: Renan Bastos <renanbastos.tec@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Co-authored-by: René <rene@gofiber.io>
334 lines
21 KiB
Markdown
334 lines
21 KiB
Markdown
---
|
|
id: csrf
|
|
---
|
|
|
|
# CSRF
|
|
|
|
The CSRF middleware for [Fiber](https://github.com/gofiber/fiber) provides protection against [Cross-Site Request Forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) (CSRF) attacks. Requests made using methods other than those defined as 'safe' by [RFC9110#section-9.2.1](https://datatracker.ietf.org/doc/html/rfc9110.html#section-9.2.1) (GET, HEAD, OPTIONS, and TRACE) are validated using tokens. If a potential attack is detected, the middleware will return a default 403 Forbidden error.
|
|
|
|
This middleware offers two [Token Validation Patterns](#token-validation-patterns): the [Double Submit Cookie Pattern (default)](#double-submit-cookie-pattern-default), and the [Synchronizer Token Pattern (with Session)](#synchronizer-token-pattern-with-session).
|
|
|
|
As a [Defense In Depth](#defense-in-depth) measure, this middleware performs [Referer Checking](#referer-checking) for HTTPS requests.
|
|
|
|
## How to use Fiber's CSRF Middleware
|
|
|
|
## Examples
|
|
|
|
Import the middleware package that is part of the Fiber web framework:
|
|
|
|
```go
|
|
import (
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/gofiber/fiber/v3/middleware/csrf"
|
|
)
|
|
```
|
|
|
|
After initializing your Fiber app, you can use the following code to initialize the middleware:
|
|
|
|
```go
|
|
// Initialize default config
|
|
app.Use(csrf.New())
|
|
|
|
// Or extend your config for customization
|
|
app.Use(csrf.New(csrf.Config{
|
|
KeyLookup: "header:X-Csrf-Token",
|
|
CookieName: "csrf_",
|
|
CookieSameSite: "Lax",
|
|
IdleTimeout: 30 * time.Minute,
|
|
KeyGenerator: utils.UUIDv4,
|
|
Extractor: func(c fiber.Ctx) (string, error) { ... },
|
|
}))
|
|
```
|
|
|
|
:::info
|
|
KeyLookup will be ignored if Extractor is explicitly set.
|
|
:::
|
|
|
|
Getting the CSRF token in a handler:
|
|
|
|
```go
|
|
func handler(c fiber.Ctx) error {
|
|
handler := csrf.HandlerFromContext(c)
|
|
token := csrf.TokenFromContext(c)
|
|
if handler == nil {
|
|
panic("csrf middleware handler not registered")
|
|
}
|
|
cfg := handler.Config
|
|
if cfg == nil {
|
|
panic("csrf middleware handler has no config")
|
|
}
|
|
if !strings.Contains(cfg.KeyLookup, ":") {
|
|
panic("invalid KeyLookup format")
|
|
}
|
|
formKey := strings.Split(cfg.KeyLookup, ":")[1]
|
|
|
|
tmpl := fmt.Sprintf(`<form action="/post" method="POST">
|
|
<input type="hidden" name="%s" value="%s">
|
|
<input type="text" name="message">
|
|
<input type="submit" value="Submit">
|
|
</form>`, formKey, token)
|
|
c.Set("Content-Type", "text/html")
|
|
return c.SendString(tmpl)
|
|
}
|
|
```
|
|
|
|
## Recipes for Common Use Cases
|
|
|
|
There are two basic use cases for the CSRF middleware:
|
|
|
|
1. **Without Sessions**: This is the simplest way to use the middleware. It uses the Double Submit Cookie Pattern and does not require a user session.
|
|
|
|
- See GoFiber recipe [CSRF](https://github.com/gofiber/recipes/tree/master/csrf) for an example of using the CSRF middleware without a user session.
|
|
|
|
2. **With Sessions**: This is generally considered more secure. It uses the Synchronizer Token Pattern and requires a user session, and the use of pre-session, which prevents login CSRF attacks.
|
|
|
|
- See GoFiber recipe [CSRF with Session](https://github.com/gofiber/recipes/tree/master/csrf-with-session) for an example of using the CSRF middleware with a user session.
|
|
|
|
## Signatures
|
|
|
|
```go
|
|
func New(config ...Config) fiber.Handler
|
|
func TokenFromContext(c fiber.Ctx) string
|
|
func HandlerFromContext(c fiber.Ctx) *Handler
|
|
|
|
func (h *Handler) DeleteToken(c fiber.Ctx) error
|
|
```
|
|
|
|
## Config
|
|
|
|
| Property | Type | Description | Default |
|
|
|:------------------|:-----------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------|
|
|
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
|
| KeyLookup | `string` | KeyLookup is a string in the form of "`<source>:<key>`" that is used to create an Extractor that extracts the token from the request. Possible values: "`header:<name>`", "`query:<name>`", "`param:<name>`", "`form:<name>`", "`cookie:<name>`". Ignored if an Extractor is explicitly set. | "header:X-CSRF-Token" |
|
|
| CookieName | `string` | Name of the csrf cookie. This cookie will store the csrf key. | "csrf_" |
|
|
| CookieDomain | `string` | Domain of the CSRF cookie. | "" |
|
|
| CookiePath | `string` | Path of the CSRF cookie. | "" |
|
|
| CookieSecure | `bool` | Indicates if the CSRF cookie is secure. | false |
|
|
| CookieHTTPOnly | `bool` | Indicates if the CSRF cookie is HTTP-only. | false |
|
|
| CookieSameSite | `string` | Value of SameSite cookie. | "Lax" |
|
|
| CookieSessionOnly | `bool` | Decides whether the cookie should last for only the browser session. (cookie expires on close). | false |
|
|
| IdleTimeout | `time.Duration` | IdleTimeout is the duration of inactivity before the CSRF token will expire. | 30 * time.Minute |
|
|
| KeyGenerator | `func() string` | KeyGenerator creates a new CSRF token. | utils.UUID |
|
|
| ErrorHandler | `fiber.ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler. | DefaultErrorHandler |
|
|
| Extractor | `func(fiber.Ctx) (string, error)` | Extractor returns the CSRF token. If set, this will be used in place of an Extractor based on KeyLookup. | Extractor based on KeyLookup |
|
|
| SingleUseToken | `bool` | SingleUseToken indicates if the CSRF token be destroyed and a new one generated on each use. (See TokenLifecycle) | false |
|
|
| Storage | `fiber.Storage` | Store is used to store the state of the middleware. | `nil` |
|
|
| Session | `*session.Store` | Session is used to store the state of the middleware. Overrides Storage if set. | `nil` |
|
|
| TrustedOrigins | `[]string` | TrustedOrigins is a list of trusted origins for unsafe requests. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. | `[]` |
|
|
|
|
### Default Config
|
|
|
|
```go
|
|
var ConfigDefault = Config{
|
|
KeyLookup: "header:" + HeaderName,
|
|
CookieName: "csrf_",
|
|
CookieSameSite: "Lax",
|
|
IdleTimeout: 30 * time.Minute,
|
|
KeyGenerator: utils.UUIDv4,
|
|
ErrorHandler: defaultErrorHandler,
|
|
Extractor: FromHeader(HeaderName),
|
|
}
|
|
```
|
|
|
|
### Recommended Config (with session)
|
|
|
|
It's recommended to use this middleware with [fiber/middleware/session](https://docs.gofiber.io/api/middleware/session) to store the CSRF token within the session. This is generally more secure than the default configuration.
|
|
|
|
```go
|
|
var ConfigDefault = Config{
|
|
KeyLookup: "header:" + HeaderName,
|
|
CookieName: "__Host-csrf_",
|
|
CookieSameSite: "Lax",
|
|
CookieSecure: true,
|
|
CookieSessionOnly: true,
|
|
CookieHTTPOnly: true,
|
|
IdleTimeout: 30 * time.Minute,
|
|
KeyGenerator: utils.UUIDv4,
|
|
ErrorHandler: defaultErrorHandler,
|
|
Extractor: FromHeader(HeaderName),
|
|
Session: session.Store,
|
|
}
|
|
```
|
|
|
|
### Trusted Origins
|
|
|
|
The `TrustedOrigins` option is used to specify a list of trusted origins for unsafe requests. This is useful when you want to allow requests from other origins. This supports matching subdomains at any level. This means you can use a value like `"https://*.example.com"` to allow any subdomain of `example.com` to submit requests, including multiple subdomain levels such as `"https://sub.sub.example.com"`.
|
|
|
|
To ensure that the provided `TrustedOrigins` origins are correctly formatted, this middleware validates and normalizes them. It checks for valid schemes, i.e., HTTP or HTTPS, and it will automatically remove trailing slashes. If the provided origin is invalid, the middleware will panic.
|
|
|
|
#### Example with Explicit Origins
|
|
|
|
In the following example, the CSRF middleware will allow requests from `trusted.example.com`, in addition to the current host.
|
|
|
|
```go
|
|
app.Use(csrf.New(csrf.Config{
|
|
TrustedOrigins: []string{"https://trusted.example.com"},
|
|
}))
|
|
```
|
|
|
|
#### Example with Subdomain Matching
|
|
|
|
In the following example, the CSRF middleware will allow requests from any subdomain of `example.com`, in addition to the current host.
|
|
|
|
```go
|
|
app.Use(csrf.New(csrf.Config{
|
|
TrustedOrigins: []string{"https://*.example.com"},
|
|
}))
|
|
```
|
|
|
|
:::caution
|
|
When using `TrustedOrigins` with subdomain matching, make sure you control and trust all the subdomains, including all subdomain levels. If not, an attacker could create a subdomain under a trusted origin and use it to send harmful requests.
|
|
:::
|
|
|
|
## Constants
|
|
|
|
```go
|
|
const (
|
|
HeaderName = "X-Csrf-Token"
|
|
)
|
|
```
|
|
|
|
## Sentinel Errors
|
|
|
|
The CSRF middleware utilizes a set of sentinel errors to handle various scenarios and communicate errors effectively. These can be used within a [custom error handler](#custom-error-handler) to handle errors returned by the middleware.
|
|
|
|
### Errors Returned to Error Handler
|
|
|
|
- `ErrTokenNotFound`: Indicates that the CSRF token was not found.
|
|
- `ErrTokenInvalid`: Indicates that the CSRF token is invalid.
|
|
- `ErrRefererNotFound`: Indicates that the referer was not supplied.
|
|
- `ErrRefererInvalid`: Indicates that the referer is invalid.
|
|
- `ErrRefererNoMatch`: Indicates that the referer does not match host and is not a trusted origin.
|
|
- `ErrOriginInvalid`: Indicates that the origin is invalid.
|
|
- `ErrOriginNoMatch`: Indicates that the origin does not match host and is not a trusted origin.
|
|
|
|
If you use the default error handler, the client will receive a 403 Forbidden error without any additional information.
|
|
|
|
## Custom Error Handler
|
|
|
|
You can use a custom error handler to handle errors returned by the CSRF middleware. The error handler is executed when an error is returned from the middleware. The error handler is passed the error returned from the middleware and the fiber.Ctx.
|
|
|
|
Example, returning a JSON response for API requests and rendering an error page for other requests:
|
|
|
|
```go
|
|
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](https://github.com/gofiber/storage/) package.
|
|
|
|
```go
|
|
storage := sqlite3.New() // From github.com/gofiber/storage/sqlite3
|
|
app.Use(csrf.New(csrf.Config{
|
|
Storage: storage,
|
|
}))
|
|
```
|
|
|
|
## How It Works
|
|
|
|
### Token Generation
|
|
|
|
CSRF tokens are generated on 'safe' requests and when the existing token has expired or hasn't been set yet. If `SingleUseToken` is `true`, a new token is generated after each use. Retrieve the CSRF token using `csrf.TokenFromContext(c)`.
|
|
|
|
### Security Considerations
|
|
|
|
This middleware is designed to protect against CSRF attacks but does not protect against other attack vectors, such as XSS. It should be used in combination with other security measures.
|
|
|
|
:::danger
|
|
Never use 'safe' methods to mutate data, for example, never use a GET request to modify a resource. This middleware will not protect against CSRF attacks on 'safe' methods.
|
|
:::
|
|
|
|
## Token Validation Patterns
|
|
|
|
### Double Submit Cookie Pattern (Default)
|
|
|
|
By default, the middleware generates and stores tokens using the `fiber.Storage` interface. These tokens are not linked to any particular user session, and they are validated using the Double Submit Cookie pattern. The token is stored in a cookie, and then sent as a header on requests. The middleware compares the cookie value with the header value to validate the token. This is a secure pattern that does not require a user session.
|
|
|
|
When the authorization status changes, the previously issued token MUST be deleted, and a new one generated. See [Token Lifecycle](#token-lifecycle) [Deleting Tokens](#deleting-tokens) for more information.
|
|
|
|
:::caution
|
|
When using this pattern, it's important to set the `CookieSameSite` option to `Lax` or `Strict` and ensure that the Extractor is not `FromCookie`, and KeyLookup is not `cookie:<name>`.
|
|
:::
|
|
|
|
:::note
|
|
When using this pattern, this middleware uses our [Storage](https://github.com/gofiber/storage) package to support various databases through a single interface. The default configuration for Storage saves data to memory. See [Custom Storage/Database](#custom-storagedatabase) for customizing the storage.
|
|
:::
|
|
|
|
### Synchronizer Token Pattern (with Session)
|
|
|
|
When using this middleware with a user session, the middleware can be configured to store the token within the session. This method is recommended when using a user session, as it is generally more secure than the Double Submit Cookie Pattern.
|
|
|
|
When using this pattern it's important to regenerate the session when the authorization status changes, this will also delete the token. See: [Token Lifecycle](#token-lifecycle) for more information.
|
|
|
|
:::caution
|
|
Pre-sessions are required and will be created automatically if not present. Use a session value to indicate authentication instead of relying on the presence of a session.
|
|
:::
|
|
|
|
## Defense In Depth
|
|
|
|
When using this middleware, it's recommended to serve your pages over HTTPS, set the `CookieSecure` option to `true`, and set the `CookieSameSite` option to `Lax` or `Strict`. This ensures that the cookie is only sent over HTTPS and not on requests from external sites.
|
|
|
|
:::note
|
|
Cookie prefixes `__Host-` and `__Secure-` can be used to further secure the cookie. Note that these prefixes are not supported by all browsers and there are other limitations. See [MDN#Set-Cookie#cookie_prefixes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes) for more information.
|
|
|
|
To use these prefixes, set the `CookieName` option to `__Host-csrf_` or `__Secure-csrf_`.
|
|
:::
|
|
|
|
## Referer Checking
|
|
|
|
For HTTPS requests, this middleware performs strict referer checking. Even if a subdomain can set or modify cookies on your domain, it can't force a user to post to your application, since that request won't come from your own exact domain.
|
|
|
|
:::caution
|
|
When HTTPS requests are protected by CSRF, referer checking is always carried out.
|
|
|
|
The Referer header is automatically included in requests by all modern browsers, including those made using the JS Fetch API. However, if you're making use of this middleware with a custom client, it's important to ensure that the client sends a valid Referer header.
|
|
:::
|
|
|
|
## Token Lifecycle
|
|
|
|
Tokens are valid until they expire or until they are deleted. By default, tokens are valid for 30 minutes, and each subsequent request extends the expiration by the idle timeout. The token only expires if the user doesn't make a request for the duration of the idle timeout.
|
|
|
|
### Token Reuse
|
|
|
|
By default, tokens may be used multiple times. If you want to delete the token after it has been used, you can set the `SingleUseToken` option to `true`. This will delete the token after it has been used, and a new token will be generated on the next request.
|
|
|
|
:::info
|
|
Using `SingleUseToken` comes with usability trade-offs and is not enabled by default. For example, it can interfere with the user experience if the user has multiple tabs open or uses the back button.
|
|
:::
|
|
|
|
### Deleting Tokens
|
|
|
|
When the authorization status changes, the CSRF token MUST be deleted, and a new one generated. This can be done by calling `handler.DeleteToken(c)`.
|
|
|
|
```go
|
|
handler := csrf.HandlerFromContext(ctx)
|
|
if handler != nil {
|
|
if err := handler.DeleteToken(app.AcquireCtx(ctx)); err != nil {
|
|
// handle error
|
|
}
|
|
}
|
|
```
|
|
|
|
:::tip
|
|
If you are using this middleware with the fiber session middleware, then you can simply call `session.Destroy()`, `session.Regenerate()`, or `session.Reset()` to delete the session and the token stored therein.
|
|
:::
|
|
|
|
## BREACH
|
|
|
|
It's important to note that the token is sent as a header on every request. If you include the token in a page that is vulnerable to [BREACH](https://en.wikipedia.org/wiki/BREACH), an attacker may be able to extract the token. To mitigate this, ensure your pages are served over HTTPS, disable HTTP compression, and implement rate limiting for requests.
|