diff --git a/README.md b/README.md
index 86b6f74..8919743 100644
--- a/README.md
+++ b/README.md
@@ -224,7 +224,7 @@ string;
Default: "__Host-psifi.x-csrf-token"
-Optional: The name of the httpOnly cookie that will be used to track CSRF protection. If you change this it is recommend that you continue to use the __Host-
or __Secure-
security prefix.
+Optional: The name of the cookie that will be used to track CSRF protection. If you change this it is recommend that you continue to use the __Host-
or __Secure-
security prefix.
Change for development
@@ -352,32 +352,37 @@ Used to customise the error response statusCode
, the contained erro
(
request: Request,
response: Response,
- overwrite?: boolean, // Set to true to force a new token to be generated
- validateOnReuse?: boolean, // Set to false to generate a new token if token re-use is invalid
+ {
+ cookieOptions?: CookieOptions, // overrides cookieOptions previously configured just for this call
+ overwrite?: boolean, // Set to true to force a new token to be generated
+ validateOnReuse?: boolean, // Set to false to generate a new token if token re-use is invalid
+ } // optional
) => string;
```
By default if a csrf-csrf cookie already exists on an incoming request, generateToken will not overwrite it, it will simply return the existing token so long as the token is valid. If you wish to force a token generation, you can use the third parameter:
```ts
-generateToken(req, res, true); // This will force a new token to be generated, and a new cookie to be set, even if one already exists
+generateToken(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. However, the cookie value will also be validated. If the validation fails an error will be thrown. If you don't want an error to be thrown, you can set the 'validateOnReuse' (by default, true) to false. In this case instead of throwing an error, a new token will be generated and returned.
```ts
-generateToken(req, res, true); // As overwrite is true, an error will never be thrown.
-generateToken(req, res, false); // As validateOnReuse is true (default), an error will be thrown if the cookie is invalid.
-generateToken(req, res, false, false); // As validateOnReuse is false, an error will never be thrown, even if the cookie is invalid. Instead, a new cookie will be generated if it is found to be invalid.
+generateToken(req, res, { overwrite: true }); // As overwrite is true, an error will never be thrown.
+generateToken(req, res, { overwrite: false }); // As validateOnReuse is true (default), an error will be thrown if the cookie is invalid.
+generateToken(req, res, { overwrite: false, validateOnReuse: false }); // As validateOnReuse is false, if the cookie is invalid a new token will be generated without any error being thrown and despite overwrite being false
```
Instead of importing and using generateToken, you can also use req.csrfToken any time after the doubleCsrfProtection middleware has executed on your incoming request.
```ts
-req.csrfToken(); // same as generateToken(req, res) and generateToken(req, res, false);
-req.csrfToken(true); // same as generateToken(req, res, true);
-req.csrfToken(false, false); // same as generateToken(req, res, false, false);
+req.csrfToken(); // same as generateToken(req, res);
+req.csrfToken({ overwrite: true }); // same as generateToken(req, res, { overwrite: true, validateOnReuse });
+req.csrfToken({ overwrite: false, validateOnReuse: false }); // same as generateToken(req, res, { overwrite: false, validateOnReuse: false });
+req.csrfToken(req, res, { overwrite: false });
+req.csrfToken(req, res, { overwrite: false, validateOnReuse: false });
```
The generateToken
function serves the purpose of establishing a CSRF (Cross-Site Request Forgery) protection mechanism by generating a token and an associated cookie. This function also provides the option to utilize a third parameter called overwrite
, and a fourth parameter called validateOnReuse
. By default, overwrite
is set to false, and validateOnReuse
is set to true.
diff --git a/example/complete/package.json b/example/complete/package.json
index a4e0bb0..95f2492 100644
--- a/example/complete/package.json
+++ b/example/complete/package.json
@@ -12,7 +12,7 @@
"license": "ISC",
"dependencies": {
"cookie-parser": "^1.4.6",
- "csrf-csrf": "latest",
- "express": "^4.18.1"
+ "csrf-csrf": "file:../..",
+ "express": "^4.19.2"
}
}
diff --git a/example/complete/src/index.js b/example/complete/src/index.js
index dd9adae..1f47e12 100644
--- a/example/complete/src/index.js
+++ b/example/complete/src/index.js
@@ -24,7 +24,7 @@ const { invalidCsrfTokenError, generateToken, doubleCsrfProtection } =
doubleCsrf({
getSecret: () => CSRF_SECRET,
cookieName: CSRF_COOKIE_NAME,
- cookieOptions: { sameSite: false, secure: false, signed: true }, // not ideal for production, development only
+ cookieOptions: { sameSite: false, secure: false }, // not ideal for production, development only
});
app.use(cookieParser(COOKIES_SECRET));
diff --git a/example/simple/package.json b/example/simple/package.json
index 4924ee9..44e9d04 100644
--- a/example/simple/package.json
+++ b/example/simple/package.json
@@ -11,8 +11,8 @@
"author": "psibean",
"license": "ISC",
"dependencies": {
- "csrf-csrf": "latest",
- "express": "^4.18.1",
+ "csrf-csrf": "../..",
+ "express": "^4.19.2",
"cookie-parser": "^1.4.6"
}
}
diff --git a/example/simple/src/index.js b/example/simple/src/index.js
index 281bf07..072b731 100644
--- a/example/simple/src/index.js
+++ b/example/simple/src/index.js
@@ -11,7 +11,7 @@ const port = 5555;
const { doubleCsrfProtection } = doubleCsrf({
getSecret: () => "this is a test", // NEVER DO THIS
cookieName: "x-csrf-test", // Prefer "__Host-" prefixed names if possible
- cookieOptions: { sameSite: false, secure: false, signed: true }, // not ideal for production, development only
+ cookieOptions: { sameSite: false, secure: false }, // not ideal for production, development only
});
app.use(cookieParser("some super secret thing, please do not copy this"));
diff --git a/src/index.ts b/src/index.ts
index ec70d42..a6b5b1d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -12,6 +12,8 @@ import type {
doubleCsrfProtection,
DoubleCsrfUtilities,
RequestMethod,
+ GenerateCsrfTokenConfig,
+ GenerateCsrfTokenOptions,
} from "./types";
export * from "./types";
@@ -23,6 +25,7 @@ export function doubleCsrf({
sameSite = "lax",
path = "/",
secure = true,
+ httpOnly = true,
...remainingCookieOptions
} = {},
size = 64,
@@ -35,10 +38,11 @@ export function doubleCsrf({
} = {},
}: DoubleCsrfConfigOptions): DoubleCsrfUtilities {
const ignoredMethodsSet = new Set(ignoredMethods);
- const cookieOptions = {
+ const defaultCookieOptions = {
sameSite,
path,
secure,
+ httpOnly,
...remainingCookieOptions,
};
@@ -48,8 +52,10 @@ export function doubleCsrf({
const generateTokenAndHash = (
req: Request,
- overwrite: boolean,
- validateOnReuse: boolean,
+ {
+ overwrite,
+ validateOnReuse,
+ }: Omit,
) => {
const getSecretResult = getSecret(req);
const possibleSecrets = Array.isArray(getSecretResult)
@@ -92,16 +98,21 @@ export function doubleCsrf({
const generateToken: CsrfTokenCreator = (
req: Request,
res: Response,
- overwrite = false,
- validateOnReuse = true,
+ {
+ cookieOptions = defaultCookieOptions,
+ overwrite = false,
+ validateOnReuse = true,
+ } = {},
) => {
- const { csrfToken, csrfTokenHash } = generateTokenAndHash(
- req,
+ const { csrfToken, csrfTokenHash } = generateTokenAndHash(req, {
overwrite,
validateOnReuse,
- );
+ });
const cookieContent = `${csrfToken}|${csrfTokenHash}`;
- res.cookie(cookieName, cookieContent, { ...cookieOptions, httpOnly: true });
+ res.cookie(cookieName, cookieContent, {
+ ...defaultCookieOptions,
+ ...cookieOptions,
+ });
return csrfToken;
};
@@ -155,8 +166,8 @@ export function doubleCsrf({
const doubleCsrfProtection: doubleCsrfProtection = (req, res, next) => {
// TODO: next major update, breaking change, make a single object parameter
- req.csrfToken = (overwrite?: boolean, validateOnReuse?: boolean) =>
- generateToken(req, res, overwrite, validateOnReuse);
+ req.csrfToken = (options: GenerateCsrfTokenOptions) =>
+ generateToken(req, res, options);
if (ignoredMethodsSet.has(req.method as RequestMethod)) {
next();
} else if (validateRequest(req)) {
diff --git a/src/tests/doublecsrf.test.ts b/src/tests/doublecsrf.test.ts
index 763d1c3..a8c8efa 100644
--- a/src/tests/doublecsrf.test.ts
+++ b/src/tests/doublecsrf.test.ts
@@ -200,11 +200,9 @@ describe("csrf-csrf token-rotation", () => {
const mockResponse = getEmptyResponse();
- const token = generateTokenWithSecret1And2(
- mockRequest,
- mockResponse,
- true,
- );
+ const token = generateTokenWithSecret1And2(mockRequest, mockResponse, {
+ overwrite: true,
+ });
attachResponseValuesToRequest({
request: mockRequest,
@@ -223,11 +221,9 @@ describe("csrf-csrf token-rotation", () => {
const mockResponse = getEmptyResponse();
- const token = generateTokenWithSecret2And1(
- mockRequest,
- mockResponse,
- true,
- );
+ const token = generateTokenWithSecret2And1(mockRequest, mockResponse, {
+ overwrite: true,
+ });
attachResponseValuesToRequest({
request: mockRequest,
diff --git a/src/tests/testsuite.ts b/src/tests/testsuite.ts
index 0f179f9..d631592 100644
--- a/src/tests/testsuite.ts
+++ b/src/tests/testsuite.ts
@@ -119,7 +119,9 @@ export const createTestSuite: CreateTestsuite = (name, doubleCsrfOptions) => {
// reset the mock response to have no cookies (in reality this would just be a new instance of Response)
mockResponse.setHeader("set-cookie", []);
- const generatedToken = generateToken(mockRequest, mockResponse, true);
+ const generatedToken = generateToken(mockRequest, mockResponse, {
+ overwrite: true,
+ });
const newCookieValue = getCookieFromResponse(mockResponse);
assert.notEqual(newCookieValue, oldCookieValue);
@@ -139,7 +141,10 @@ export const createTestSuite: CreateTestsuite = (name, doubleCsrfOptions) => {
(decodedCookieValue as string).split("|")[0] + "|invalid-hash");
expect(() =>
- generateToken(mockRequest, mockResponse, false, true),
+ generateToken(mockRequest, mockResponse, {
+ overwrite: false,
+ validateOnReuse: true,
+ }),
).to.throw(invalidCsrfTokenError.message);
// just an invalid value in the cookie
@@ -151,7 +156,10 @@ export const createTestSuite: CreateTestsuite = (name, doubleCsrfOptions) => {
: (mockRequest.cookies[cookieName] = "invalid-value");
expect(() =>
- generateToken(mockRequest, mockResponse, false, true),
+ generateToken(mockRequest, mockResponse, {
+ overwrite: false,
+ validateOnReuse: true,
+ }),
).to.throw(invalidCsrfTokenError.message);
});
@@ -178,12 +186,10 @@ export const createTestSuite: CreateTestsuite = (name, doubleCsrfOptions) => {
(decodedCookieValue as string).split("|")[0] + "|invalid-hash");
assert.doesNotThrow(
() =>
- (generatedToken = generateToken(
- mockRequest,
- mockResponse,
- false,
- false,
- )),
+ (generatedToken = generateToken(mockRequest, mockResponse, {
+ overwrite: false,
+ validateOnReuse: false,
+ })),
);
newCookieValue = getCookieFromResponse(mockResponse);
assert.notEqual(newCookieValue, oldCookieValue);
@@ -199,12 +205,10 @@ export const createTestSuite: CreateTestsuite = (name, doubleCsrfOptions) => {
assert.doesNotThrow(
() =>
- (generatedToken = generateToken(
- mockRequest,
- mockResponse,
- false,
- false,
- )),
+ (generatedToken = generateToken(mockRequest, mockResponse, {
+ overwrite: false,
+ validateOnReuse: false,
+ })),
);
newCookieValue = getCookieFromResponse(mockResponse);
diff --git a/src/types.ts b/src/types.ts
index 142d90c..f232c91 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,7 +3,7 @@ import type { HttpError } from "http-errors";
export type SameSiteType = boolean | "lax" | "strict" | "none";
export type TokenRetriever = (req: Request) => string | null | undefined;
-export type DoubleCsrfCookieOptions = Omit;
+export type CsrfTokenCookieOverrides = Omit;
declare module "http" {
interface IncomingHttpHeaders {
"x-csrf-token"?: string | undefined;
@@ -12,7 +12,9 @@ declare module "http" {
declare module "express-serve-static-core" {
export interface Request {
- csrfToken?: (overwrite?: boolean) => ReturnType;
+ csrfToken?: (
+ options?: GenerateCsrfTokenOptions,
+ ) => ReturnType;
}
}
@@ -46,13 +48,12 @@ export type CsrfCookieSetter = (
res: Response,
name: string,
value: string,
- options: DoubleCsrfCookieOptions,
+ options: CookieOptions,
) => void;
export type CsrfTokenCreator = (
req: Request,
res: Response,
- ovewrite?: boolean,
- validateOnReuse?: boolean,
+ options?: GenerateCsrfTokenOptions,
) => string;
export type CsrfErrorConfig = {
statusCode: number;
@@ -60,7 +61,12 @@ export type CsrfErrorConfig = {
code: string | undefined;
};
export type CsrfErrorConfigOptions = Partial;
-
+export type GenerateCsrfTokenConfig = {
+ overwrite: boolean;
+ validateOnReuse: boolean;
+ cookieOptions: CsrfTokenCookieOverrides;
+};
+export type GenerateCsrfTokenOptions = Partial;
export interface DoubleCsrfConfig {
/**
* A function that returns a secret or an array of secrets.
@@ -98,7 +104,7 @@ export interface DoubleCsrfConfig {
* The options for HTTPOnly cookie that will be set on the response.
* @default { sameSite: "lax", path: "/", secure: true }
*/
- cookieOptions: DoubleCsrfCookieOptions;
+ cookieOptions: CookieOptions;
/**
* The methods that will be ignored by the middleware.