Skip to content

Commit

Permalink
📄 docs: improve csrf docs (#2726)
Browse files Browse the repository at this point in the history
* docs: improve csrf docs

- fix issues with `X-Csrf-Token` capitalization inconsistency.
- reduce redundancy and repetition.
- improve grammar.

* docs: update middleware description

* docs: within vs in

* docs: deleting tokens

* docs: MUST

* docs: add colon

* docs: all modern browsers

* docs: patterns

* docs: improve phrasing of pattern options
  • Loading branch information
sixcolors committed Nov 16, 2023
1 parent 3ab3d15 commit 2374cad
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 28 deletions.
48 changes: 24 additions & 24 deletions docs/api/middleware/csrf.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ 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 using tokens. These tokens verify 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) (Safe-Methods: GET, HEAD, OPTIONS, and TRACE). If a potential attack is detected this middleware will, by default, return a 403 Forbidden error.
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 can be used with or without a user session and offers two token validation patterns. In addition, it implements strict referer checking for HTTPS requests, ensuring the security of your application. For HTTPS requests, 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.
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-session).

As a [Defense In Depth](#defense-in-depth) measure, this middleware performs [Referer Checking](#referer-checking) for HTTPS requests.

## 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 `c.Locals(contextKey)`, where `contextKey` is defined in the configuration.
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 `c.Locals(contextKey)`, where `contextKey` is defined within the configuration.

## Security Considerations

Expand All @@ -24,34 +26,34 @@ Never use 'safe' methods to mutate data, for example, never use a GET request to

#### Double Submit Cookie Pattern (Default)

In the default configuration, the middleware generates and stores tokens using the `fiber.Storage` interface. These tokens are not associated with a user session, and a Double Submit Cookie pattern is used to validate the token. The token is stored in a cookie and 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.
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 using this pattern, it's important to delete the token when the authorization status changes, see: [Token Lifecycle](#token-lifecycle) for more information.
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 method, it's important to set the `CookieSameSite` option to `Lax` or `Strict` and ensure that the Extractor is not `CsrfFromCookie`, and KeyLookup is not `cookie:<name>`.
When using this pattern, it's important to set the `CookieSameSite` option to `Lax` or `Strict` and ensure that the Extractor is not `CsrfFromCookie`, 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 (Session)
#### Synchronizer Token Pattern (with Session)

When using this middleware with a user session, the middleware can be configured to store the token in 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 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
When using this method, pre-sessions are required and will be created if a session is not already present. This means the middleware will create a session for every safe request, even if the request does not require a session. Therefore, the existence of a session should not be used to indicate that a user is logged in or authenticated; a session value should be used for this purpose.
Pre-sessions are required and will be created automatically if not present. Use a session value to indicate authentication instead of relying on 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. However, these prefixes are not supported by all browsers and there are some 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.
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_`.
:::
Expand All @@ -61,7 +63,9 @@ To use these prefixes, set the `CookieName` option to `__Host-csrf_` or `__Secur
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
Referer checking is required for https requests protected by CSRF. All modern browsers will automatically include the Referer header in requests, including those made with the JS Fetch API. However, if you are using this middleware with a custom client you must ensure that the client sends a valid Referer header.
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.
:::


Expand All @@ -74,15 +78,15 @@ Tokens are valid until they expire or until they are deleted. By default, tokens
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. It can interfere with the user experience if the user has multiple tabs open or uses the back button.
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
if handler, ok := app.AcquireCtx(ctx).Locals(ConfigDefault.HandlerContextKey).(*CSRFHandler); ok {
if handler, ok := app.AcquireCtx(ctx).Locals(csrf.ConfigDefault.HandlerContextKey).(*CSRFHandler); ok {
if err := handler.DeleteToken(app.AcquireCtx(ctx)); err != nil {
// handle error
}
Expand Down Expand Up @@ -127,20 +131,15 @@ app.Use(csrf.New(csrf.Config{
CookieSameSite: "Lax",
Expiration: 1 * time.Hour,
KeyGenerator: utils.UUIDv4,
Extractor: func(c *fiber.Ctx) (string, error) { ... },
}))
```

:::info
KeyLookup will be ignored if Extractor is explicitly set.
:::

## 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" |
| 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. | "" |
Expand All @@ -152,8 +151,8 @@ KeyLookup will be ignored if Extractor is explicitly set.
| 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` |
| SessionKey | `string` | SessionKey is the key used to store the token in the session. | "fiber.csrf.token" |
| ContextKey | `string` | Context key to store the generated CSRF token into the context. If left empty, the token will not be stored in the context. | "" |
| SessionKey | `string` | SessionKey is the key used to store the token within the session. | "fiber.csrf.token" |
| ContextKey | `string` | Context key to store the generated CSRF token into the context. If left empty, the token will not be stored within the context. | "" |
| KeyGenerator | `func() string` | KeyGenerator creates a new CSRF token. | utils.UUID |
| CookieExpires | `time.Duration` (Deprecated) | Deprecated: Please use Expiration. | 0 |
| Cookie | `*fiber.Cookie` (Deprecated) | Deprecated: Please use Cookie* related fields. | `nil` |
Expand All @@ -180,13 +179,14 @@ var ConfigDefault = Config{

### 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 in the session. This is generally more secure than the default configuration.
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: "csrf_",
CookieName: "__Host-csrf_",
CookieSameSite: "Lax",
CookieSecure: true,
CookieSessionOnly: true,
CookieHTTPOnly: true,
Expiration: 1 * time.Hour,
Expand Down Expand Up @@ -218,7 +218,7 @@ The CSRF middleware utilizes a set of sentinel errors to handle various scenario
- `ErrNoReferer`: Indicates that the referer was not supplied.
- `ErrBadReferer`: Indicates that the referer is invalid.

If you are using the default error handler, it will return a 403 Forbidden error for any of these errors without providing any additional information to the client.
If you use the default error handler, the client will receive a 403 Forbidden error without any additional information.

## Custom Error Handler

Expand Down
2 changes: 1 addition & 1 deletion middleware/csrf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Config struct {
//
// Ignored if an Extractor is explicitly set.
//
// Optional. Default: "header:X-CSRF-Token"
// Optional. Default: "header:X-Csrf-Token"
KeyLookup string

// Name of the session cookie. This cookie will store session key.
Expand Down
6 changes: 3 additions & 3 deletions middleware/csrf/csrf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func Test_CSRF_MultiUseToken(t *testing.T) {
app := fiber.New()

app.Use(New(Config{
KeyLookup: "header:X-CSRF-Token",
KeyLookup: "header:X-Csrf-Token",
}))

app.Post("/", func(c *fiber.Ctx) error {
Expand All @@ -292,7 +292,7 @@ func Test_CSRF_MultiUseToken(t *testing.T) {

// Invalid CSRF token
ctx.Request.Header.SetMethod(fiber.MethodPost)
ctx.Request.Header.Set("X-CSRF-Token", "johndoe")
ctx.Request.Header.Set("X-Csrf-Token", "johndoe")
h(ctx)
utils.AssertEqual(t, 403, ctx.Response.StatusCode())

Expand All @@ -307,7 +307,7 @@ func Test_CSRF_MultiUseToken(t *testing.T) {
ctx.Request.Reset()
ctx.Response.Reset()
ctx.Request.Header.SetMethod(fiber.MethodPost)
ctx.Request.Header.Set("X-CSRF-Token", token)
ctx.Request.Header.Set("X-Csrf-Token", token)
ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
h(ctx)
newToken := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
Expand Down

0 comments on commit 2374cad

Please sign in to comment.