Skip to content

[V3] CookieFrom request and TokenFromRequest doesn't have the same form #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
chemisax opened this issue Mar 29, 2025 · 3 comments
Closed

Comments

@chemisax
Copy link

Hello,

It took me a lot of time to debug why my implementation was failing.

I've found that in the validateRequest function, the cookie is being split into token and hash, but the header content its not. I don't know if this is intentional or should I send the header without the hash from the front.

https://github.com/Psifi-Solutions/csrf-csrf/blob/v3.x.x/src/index.ts#L160

I've fixed with this configuration:

doubleCsrf({
      getSecret: () => configService.getOrThrow('csrf.secret'),
      getTokenFromRequest: (request) => {
        return String(request.headers['x-xsrf-token']).split('|')[0];
      },
      cookieName: 'XSRF-TOKEN',
      cookieOptions: {
        sameSite: 'lax',
        httpOnly: false,
        path: '/',
        secure: configService.getOrThrow('csrf.cookie.secure'),
        maxAge: configService.getOrThrow('csrf.cookie.maxAge'),
      },
    });

But using String(request.headers['x-xsrf-token']).split('|')[0]; doesn't feel right.

Is this intentional or am I missing something?

@psibean
Copy link
Contributor

psibean commented Mar 29, 2025

For version 3, yes, this is intended. The cookie value, based on version 3, has a preference to be httpOnly, and as you've found, even if you don't use httpOnly, it's a little awkward to use that cookie as you'll need to split it to get just the token value.

There's been a variety of discussion over the benefits of using httpOnly for the double submit pattern. For instance there's a discussion here regarding a case where Twitter had a vulnerability on a subdomain, and through this vulnerability, was able to craft requests that directed attacks against users against the main domain. In this particular scenario, had the CSRF cookie been httpOnly, it would have prevented the additional impact against the primary domain.

Additionally there are always confusions on this topic even from OWASP maintainers themselves - where here they recommend it..

If you're running an SPA, even Okta (a reputable security source) recommend using httpOnly and getting your CSRF token via a request.

generateToken, req.csrfToken - these return just the token value (without the hash). The idea in this case is, you provide your frontend with the return value, not the value from the cookie. The only reason the plain token exists in thee cookie is so that it can be re-used in a stateless way.

The implementation has changed greatly for the next version (v4, not yet released), where a hmac and a nonce are used to form the CSRF token. This way the cookie value can be used directly, the hmac can be reconstructed by using the nonce and the users session id. For v4 httpOnly will be false by default, and it'll be more on the discretion of the developer to ensure they're configuring it correctly for their usecase.

@chemisax
Copy link
Author

@psibean

Thank you for your detailed response! I'm currently working on rebuilding an api for a SPA from laravel to nest.js.

I like this solution ↓ (It means I need to update the SPA also, but thats not a big deal)

If you're running an SPA, even Okta (a reputable security source) recommend using httpOnly and getting your CSRF token via a request.

@psibean
Copy link
Contributor

psibean commented Mar 29, 2025

I like this solution ↓ (It means I need to update the SPA also, but thats not a big deal)

Do make sure you provide the getSessionIdentifier option, it's especially necessary if you're using an endpoint specifically to request a token. In v3 the option is optional, but it'll be required by v4. The purpose is to ensure a requested token can only be used by the user who requested it.

Otherwise anyone could request a CSRF token and it would be valid for any other user.

Note that, despite the name, getSessionIdentifier just needs to return some randomly unique value that identifies the user of the request. Ideally it's something that is different every time the user logs in, such as a JWT, not necessarily a session specifically, unless you're using sessions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants