diff --git a/README.md b/README.md index 5ee7d94..f943396 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ string; ```

- Optional
+ Optional
Default:

@@ -403,7 +403,7 @@ Used to customise the error response statusCode, the contained erro { cookieOptions?: CookieOptions, // allows overriding of cookieOptions overwrite?: boolean, // Set to true to force a new token to be generated - validateOnReuse?: boolean, // Set to true to throw an error when overwrite is false and the current CSRF token is invalid + validateOnReuse?: boolean, // Deprecated, leave as default } // optional ) => string; ``` @@ -414,33 +414,31 @@ Used to customise the error response statusCode, the contained erro generateCsrfToken(req, res, { overwrite: true }); // This will force a new token to be generated, and a new cookie to be set, even if one already exists ``` -

If the overwrite parameter is set to false (default), the existing token will be re-used and returned. If the current / existing CSRF token is not valid, then a new token will be generated without any error being thrown. If you want the generateCsrfToken to throw an error instead, provide the validateOnReuse: true option.

+

If the overwrite parameter is set to false (default), the existing token will be re-used and returned.

If overwrite is true a new token will always be generated, even if the current one is invalid.

```ts -generateCsrfToken(req, res, { overwrite: true }); // As overwrite is true, an error will never be thrown. -generateCsrfToken(req, res, { overwrite: false }); // As validateOnReuse is false (default), if the current CSRF token from the cookie is invalid, a new token will be generated without any error being thrown. +generateCsrfToken(req, res, { overwrite: true }); // As overwrite is true a new CSRF token will be generated. +generateCsrfToken(req, res, { overwrite: false }); // As overwrite is false, the existing CSRF token will be reused from the CSRF token cookie generateCsrfToken(req, res); // same as previous -generateCsrfToken(req, res, { overwrite: false, validateOnReuse: true }); // As validateOnReuse is true, if the CSRF token from the cookie is invalid, a new token will be generated without an error being thrown. +generateCsrfToken(req, res, { overwrite: false, validateOnReuse: true }); // DEPRECATED - As validateOnReuse is true, if the CSRF token from the cookie is invalid, an error will be thrown ```

Instead of importing and using generateCsrfToken, you can also use req.csrfToken any time after the doubleCsrfProtection middleware has executed on your incoming request.

```ts req.csrfToken(); // same as generateCsrfToken(req, res); -req.csrfToken({ overwrite: true }); // same as generateCsrfToken(req, res, { overwrite: true, validateOnReuse }); -req.csrfToken({ overwrite: false, validateOnReuse: false }); // same as generateCsrfToken(req, res, { overwrite: false, validateOnReuse: false }); +req.csrfToken({ overwrite: true }); // same as generateCsrfToken(req, res, { overwrite: true }); req.csrfToken(req, res, { overwrite: false }); -req.csrfToken(req, res, { overwrite: false, validateOnReuse: false }); ``` -

The generateCsrfToken function serves the purpose of establishing a CSRF protection mechanism by generating a token and an associated cookie. This function also provides the option to utilise a third parameter, which is an object that may contain: overwrite, validateOnReuse, or cookieOptions. By default, overwrite and validateOnReuse are both set to false. cookieOptions if not provided will just default to the options originally provided to the initialisation configuration, any options that are provided will override those initially provided.

+

The generateCsrfToken function serves the purpose of establishing a CSRF protection mechanism by generating a token and an associated cookie. This function also provides the option to utilise a third parameter, which is an object that may contain: overwrite, and cookieOptions. By default, overwrite is set to false. cookieOptions if not provided will just default to the options originally provided to the initialisation configuration, any options that are provided will override those initially provided.

It returns a CSRF token and attaches a cookie to the response object. The cookie content is `${hmac}${csrfTokenDelimiter}${randomValue}`.

In some cases you should only transmit your token to the frontend as part of a response payload. Consult the "Do I need csrf-csrf?" and "Does httpOnly have to be true?" sections of the FAQ.

When overwrite is set to false, the function behaves in a way that preserves the existing CSRF token. In other words, if a valid CSRF token is already present in the incoming request cookie, the function will reuse the existing CSRF token.

If overwrite is set to true, the function will generate a new token and cookie each time it is invoked. This behavior can potentially lead to certain complications, particularly when multiple tabs are being used to interact with your web application. In such scenarios, the creation of new cookies with every call to the function can disrupt the proper functioning of your web app across different tabs, as the changes might not be synchronised effectively (you would need to write your own synchronisation logic).

-

If overwrite is set to false, the function will also validate the existing cookie information. If the information is found to be invalid, a new token will be generated and returned. If you want an error to be thrown when validation fails during generation you can set the validateOnReuse (by default, false) to true. If it is true then an error will be thrown instead of a new token being generated.

+

If overwrite is set to false, the function will return the existing CSRF token from the existing CSRF token cookie.

invalidCsrfTokenError

diff --git a/src/index.ts b/src/index.ts index f7aa6a1..3c2328f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -69,21 +69,15 @@ export function doubleCsrf({ const possibleSecrets = getPossibleSecrets(req); // If cookie is not present, this is a new user (no existing csrfToken) // If ovewrite is true, always generate a new token. - // If overwrite is false and there is no existing token, generate a new token. - // If overwrite is false and there is an existin token then validate the token and hash pair - // the existing cookie and reuse it if it is valid. If it isn't valid, then either throw or - // generate a new token based on validateOnReuse. + // If overwrite is false and validateOnReuse is true and there is an existing token, validate it first + // If overwrite is false and validateOnReuse is false, just return the cookie value if (cookieName in req.cookies && !overwrite) { - if (validateCsrfToken(req, possibleSecrets)) { - // If the token is valid, reuse it + if (!validateOnReuse || (validateOnReuse && validateCsrfToken(req, possibleSecrets))) { + // If validateOnReuse is false, or if the token is valid, reuse it return getCsrfTokenFromCookie(req); } - - if (validateOnReuse) { - // If the pair is invalid, but we want to validate on generation, throw an error - // only if the option is set - throw invalidCsrfTokenError; - } + // This only happens if overwrite is false and validateOnReuse is true + throw invalidCsrfTokenError; } // otherwise, generate a completely new token // the 'newest' or preferred secret is the first one in the array diff --git a/src/tests/testsuite.ts b/src/tests/testsuite.ts index 6f646d8..9b59f87 100644 --- a/src/tests/testsuite.ts +++ b/src/tests/testsuite.ts @@ -170,6 +170,15 @@ export const createTestSuite: CreateTestsuite = (name, doubleCsrfOptions) => { expect(newCookieValue).not.toBe(oldCookieValue); expect(generatedToken).not.toBe(csrfToken); }); + + it("should return existing CSRF token for a GET request that does not include the CSRF token", () => { + const { mockRequest, mockResponse, csrfToken } = generateMocksWithTokenInternal(); + mockRequest.method = "GET"; + mockRequest.headers[HEADER_KEY] = undefined; + const reusedToken = generateCsrfToken(mockRequest, mockResponse); + + expect(reusedToken).toBe(csrfToken); + }); }); describe("validateRequest", () => { diff --git a/src/types.ts b/src/types.ts index 23e0231..e25fdc0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,6 +37,9 @@ export type CsrfErrorConfig = { export type CsrfErrorConfigOptions = Partial; export type GenerateCsrfTokenConfig = { overwrite: boolean; + /** + * @deprecated leave this as the default value, to be removed in the future + */ validateOnReuse: boolean; cookieOptions: CsrfTokenCookieOptions; };