Skip to content

Commit

Permalink
feat: support custom delimiter for cookie value separation
Browse files Browse the repository at this point in the history
When storing the value in the cookie both the non-hashed token and the
hashed token are concatenated together separated by a delimiter. This
is so that stateless re-use of the token value can be supported.

Previously the delimiter was hardcoded to a '|' character, this can now
be overridden via the delimiter configuration option.
  • Loading branch information
psibean committed Nov 25, 2024
1 parent 3a016c5 commit 0a9c595
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 3 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,19 @@ string;

<p>For development you will need to set <code>secure</code> to false unless you're running HTTPS locally. Ensure secure is true in your live environment by using environment variables.</b></p>

<h3>delimiter</h3>

```ts
string;
```

<p>
<b>Optional<br />
Default: <code>"|"</code></b>
</p>

<p>The delimiter is used when concatenating the plain CSRF token with the hash, constructing the value for the cookie. It is also used when splitting the cookie value. This is how a token can be reused when there is no state. Note that the plain token value within the cookie is only intended to be used for token re-use, it is not used as the source for token validation.</p>

<h3>getTokenFromRequest</h3>

```ts
Expand Down
7 changes: 4 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function doubleCsrf({
httpOnly = true,
...remainingCookieOptions
} = {},
delimiter = "|",
size = 64,
ignoredMethods = ["GET", "HEAD", "OPTIONS"],
getTokenFromRequest = (req) => req.headers["x-csrf-token"],
Expand Down Expand Up @@ -70,7 +71,7 @@ export function doubleCsrf({
// 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 (typeof csrfCookie === "string" && !overwrite) {
const [csrfToken, csrfTokenHash] = csrfCookie.split("|");
const [csrfToken, csrfTokenHash] = csrfCookie.split(delimiter);
if (
validateTokenAndHashPair({
csrfToken,
Expand Down Expand Up @@ -116,7 +117,7 @@ export function doubleCsrf({
overwrite,
validateOnReuse,
});
const cookieContent = `${csrfToken}|${csrfTokenHash}`;
const cookieContent = `${csrfToken}${delimiter}${csrfTokenHash}`;
res.cookie(cookieName, cookieContent, {
...defaultCookieOptions,
...cookieOptions,
Expand Down Expand Up @@ -154,7 +155,7 @@ export function doubleCsrf({
if (typeof csrfCookie !== "string") return false;

// cookie has the form {token}|{hash}
const [csrfToken, csrfTokenHash] = csrfCookie.split("|");
const [csrfToken, csrfTokenHash] = csrfCookie.split(delimiter);

// csrf token from the request
const csrfTokenFromRequest = getTokenFromRequest(req) as string;
Expand Down
2 changes: 2 additions & 0 deletions src/tests/doublecsrf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { HEADER_KEY } from "./utils/constants.js";

createTestSuite("csrf-csrf unsigned, single secret", {
getSecret: getSingleSecret,
delimiter: "~",
});
createTestSuite("csrf-csrf signed, single secret", {
cookieOptions: { signed: true },
Expand All @@ -28,6 +29,7 @@ createTestSuite("csrf-csrf signed with custom options, single secret", {
cookieOptions: { signed: true, sameSite: "strict" },
size: 128,
cookieName: "__Host.test-the-thing.token",
delimiter: ":",
});

createTestSuite("csrf-csrf unsigned, multiple secrets", {
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ export interface DoubleCsrfConfig {
*/
cookieOptions: CookieOptions;

/**
* Used to separate the plain token and the token hash in the cookie value.
*/
delimiter: string;

/**
* The methods that will be ignored by the middleware.
* @default ["GET", "HEAD", "OPTIONS"]
Expand Down

0 comments on commit 0a9c595

Please sign in to comment.