📦 payloadcms / payload

📄 cookies.mdx · 133 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133---
title: Cookie Strategy
label: Cookie Strategy
order: 40
desc: Enable HTTP Cookie based authentication to interface with Payload.
keywords: authentication, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---

Payload offers the ability to [Authenticate](./overview) via HTTP-only cookies. These can be read from the responses of `login`, `logout`, `refresh`, and `me` auth operations.

<Banner type="success">
  **Tip:** You can access the logged-in user from within [Access
  Control](../access-control/overview) and [Hooks](../hooks/overview) through
  the `req.user` argument. [More details](./token-data).
</Banner>

### Automatic browser inclusion

Modern browsers automatically include `http-only` cookies when making requests directly to URLs—meaning that if you are running your API on `https://example.com`, and you have logged in and visit `https://example.com/test-page`, your browser will automatically include the Payload authentication cookie for you.

### HTTP Authentication

However, if you use `fetch` or similar APIs to retrieve Payload resources from its REST or GraphQL API, you must specify to include credentials (cookies).

Fetch example, including credentials:

```ts
const response = await fetch('http://localhost:3000/api/pages', {
  credentials: 'include',
})

const pages = await response.json()
```

For more about including cookies in requests from your app to your Payload API, [read the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#including_credentials).

<Banner type="success">
  **Tip:** To make sure you have a Payload cookie set properly in your browser
  after logging in, you can use the browsers Developer Tools > Application >
  Cookies > [your-domain-here]. The Developer tools will still show HTTP-only
  cookies.
</Banner>

### CSRF Attacks

CSRF (cross-site request forgery) attacks are common and dangerous. By using an HTTP-only cookie, Payload removes many XSS vulnerabilities, however, CSRF attacks can still be possible.

For example, let's say you have a popular app `https://payload-finances.com` that allows users to manage finances, send and receive money. As Payload is using HTTP-only cookies, that means that browsers automatically will include cookies when sending requests to your domain - **no matter what page created the request**.

So, if a user of `https://payload-finances.com` is logged in and is browsing around on the internet, they might stumble onto a page with malicious intent. Let's look at an example:

```ts
// malicious-intent.com
// makes an authenticated request as on your behalf

const maliciousRequest = await fetch(`https://payload-finances.com/api/me`, {
  credentials: 'include',
}).then((res) => await res.json())
```

In this scenario, if your cookie was still valid, malicious-intent.com would be able to make requests like the one above on your behalf. This is a CSRF attack.

### CSRF Prevention

Define domains that you trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:

```ts
// payload.config.ts

import { buildConfig } from 'payload'

const config = buildConfig({
  serverURL: 'https://my-payload-instance.com',
  // highlight-start
  csrf: [
    // whitelist of domains to allow cookie auth from
    'https://your-frontend-app.com',
    'https://your-other-frontend-app.com',
    // `config.serverURL` is added by default if defined
  ],
  // highlight-end
  collections: [
    // collections here
  ],
})

export default config
```

#### Cross domain authentication

If your frontend is on a different domain than your Payload API then you will not be able to use HTTP-only cookies for authentication by default as they will be considered third-party cookies by the browser.
There are a few strategies to get around this:

##### 1. Use subdomains

Cookies can cross subdomains without being considered third party cookies, for example if your API is at api.example.com then you can authenticate from example.com.

##### 2. Configure cookies

If option 1 isn't possible, then you can get around this limitation by [configuring your cookies](./overview#config-options) on your authentication collection to achieve the following setup:

```
SameSite: None // allows the cookie to cross domains
Secure: true // ensures it's sent over HTTPS only
HttpOnly: true // ensures it's not accessible via client side JavaScript
```

Configuration example:

```ts
{
  slug: 'users',
  auth: {
    cookies: {
      sameSite: 'None',
      secure: true,
    }
  },
  fields: [
    // your auth fields here
  ]
},
```

If you're configuring [cors](../production/preventing-abuse#cross-origin-resource-sharing-cors) in your Payload config, you won't be able to use a wildcard anymore, you'll need to specify the list of allowed domains.

<Banner type="success">
  **Good to know:** Setting up `secure: true` will not work if you're developing
  on `http://localhost` or any non-https domain. For local development you
  should conditionally set this to `false` based on the environment.
</Banner>