Skip to content

OAuth state query parameter can't be connected to a given user - state re-use (CSRF)

I found this while working on a different ~security issue and adding a CSRF nonce query parameter to a separate endpoint, /login/upgrade


We use TokenStateProvider to generate and verify a state token used in the GitHub OAuth process,

  • https://github.com/login/oauth/authorize?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Flogin%2Fcallback&scope=user%3Aemail%2Cread%3Aorg&state=xxx&client_id=xxx
  • http://localhost:5000/login/callback?code=xxx&state=xxx

With TokenStateProvider, the state is made from the current time and a random salt (it doesn't matter that the string is encrypted) which we have no way to actually connect to the user who generated it. I am concerned that this isn't really adding any protection since an attacker can just generate their own state and re-use it in some CSRF (against some other users requests). All this seems to do currently is make sure a request was made within the maxTokenAge time period.

It seems like we need to add some additional information into the string that connects that string to the user later on. Since the user isn't signed in at this point, session makes sense 🤔

The alternative SessionStateProvider makes a bit more sense to me because you generate a state, then can verify it later down the road because we can compare what they have in their session to the query parameter

It looks like we moved to TokenStateProvider because we didn't want to rely on req.session. I'm not sure of root cause for switching but the following comment maybe hints at people not making it through the sign in fast enough,

The token state provider is useful if you want to use OAuth state verification without using session state on the client. It's also useful if you users take longer than your session timeout to complete the OAuth login.

https://github.com/troupe/passport-oauth2/commit/7220f73f3a0cb65054185abf4260aac9c3a405b4#diff-edc11a90ed8003f64fbeb053ba2c3801R7

I haven't looked into any actual attacks but something just seems a bit fishy.

Am I missing something?

Maintainer spotted this problem a while ago

Just saw this comment on the PR against the original project that comes to the same conclusion,

I've also removed the TokenStateProvider. I don't want the responsibility of maintenance for that code, so if you need it, feel free to package it as a separate module. I'd caution, however, that in its current implementation, it does not cryptographically bind the token to the current session, which leaves open the possibility of XSRF attacks.

@jaredhanson, https://github.com/jaredhanson/passport-oauth2/pull/24#issuecomment-181685311

cc @andrewn @dappelt

Edited by Eric Eastwood