Cookie mode and CSRF
For browser applications, keep the tokens in httpOnly cookies. The JavaScript of the page never sees them: a script injected in the page (XSS) cannot steal them, while a token stored in localStorage or in memory can be read and sent anywhere.
Enable it
The browser logs in with mode: 'cookie', and sends the cookies with each request:
A login without mode: 'cookie' gets the tokens in the body, as usual: the same API serves the browser and the mobile application.
The modes
A token set as a cookie is never returned in the body. Prefer the last mode: with the access token in the body, the page has to keep it in JavaScript.
Restoring the session
When the page loads, it does not know whether the user is logged in: the cookies are out of its reach. POST /auth/restore answers with the user and the expiration of the session, or 401:
Then refresh before expiresAt with POST /auth/refresh: the cookies are renewed. /auth/restore requires cookies.accessTokenInCookie.
The cookies
- The prefixes protect the cookies:
__Host-(secure, path/, nodomain) prevents a subdomain or an insecure page from overwriting the cookie,__Secure-prevents an insecure page from overwriting it. - The refresh token is only sent to the auth routes, never to the rest of the API, and never with a navigation from another site (
SameSite=Strict). - Its path follows the prefix of the plugin and the base path of the app.
cookies.refreshTokenPathforces another one. - Both cookies live as long as the refresh token, not
accessTokenTTL: a refresh needs the access token, even expired. - The path of the refresh token cookie must be the browser path of the auth routes: with a wrong path, the browser does not send it, and refreshes fail with
401 NO_TOKENS_PROVIDED. It is set byauthPlugin(), after thebasePathorpublicBasePathof the app; without the plugin, it is/auth. An auth service serves one path: create one per app when several apps serve the auth routes under different paths. - Names: without
secure, the default names have no prefix. Custom names (accessTokenName,refreshTokenName) are kept as is, and checked at startup: a__Host-or__Secure-name without the attributes it requires would be silently rejected by browsers. secureistrueby default. Browsers accept secure cookies onhttp://localhost; setsecure: falseonly for another plain HTTP host in development.
Every setting of the cookie mode is listed in the Auth reference.
Protection against cross-site request forgery
The browser attaches cookies to every request, including the ones that another site makes it send: that is cross-site request forgery (CSRF). In cookie mode, the plugin checks where the requests authenticated by a cookie come from, with the headers set by the browser (Sec-Fetch-Site, then Origin for older browsers), as the OWASP recommends. Clients need no CSRF token.
- Requests from the origin of the API are accepted, as well as requests without these headers (clients that are not browsers).
- Requests from another origin, including another subdomain of the same site, are accepted only from trusted origins.
- Otherwise, the access token cookie is ignored (the request is anonymous, and a warning is logged), and
/auth/refresh,/auth/restoreand/auth/logoutanswer403 CROSS_ORIGIN_REQUEST. GET,HEADandOPTIONSrequests are not checked: they must not change data. Never change data in aGETroute.- Requests authenticated by the
Authorizationheader are not checked: another site cannot set it.
Trusted origins
By default, the trusted origins are those of the cors option of the app, when it allows credentials. A front-end on another origin needs this CORS configuration anyway to use the cookies:
cookies.trustedOrigins replaces them ([] trusts no other origin):
For older browsers without Sec-Fetch-Site, the Origin is compared with the Host header: behind a proxy, forward the original Host, or list the origin in trustedOrigins.
Routes that read the cookies
A route of your app that reads the auth cookies itself applies the same check with await auth.isCookieAuthenticationAllowed(ctx.req): it returns a promise, so never test it without await.
An application on another site
The defaults suit an application served from the same site as the API (app.example.com and api.example.com). For an application on another site (example.app for api.example.com), the cookies need sameSite: 'none': they are then sent with cross-site requests, and the CSRF protection above is what keeps them safe. Prefer serving the application and the API from the same site when you can.
The tokens set as cookies are no longer returned in the body, the cookies get the __Host- and __Secure- prefixes, the refresh token cookie is restricted to the auth routes, and the cookie mode is protected against CSRF. To keep the sessions of your users when upgrading, see Migrate from v1.