grant icon indicating copy to clipboard operation
grant copied to clipboard

SameSite=Strict cookies break the oauth flow (Grant: missing session or misconfigured provider)

Open vicb opened this issue 5 years ago • 12 comments
trafficstars

TL;DR do no use strict SameSite cookies.

From MDN:

Strict SameSite Cookies will only be sent in a first-party context and not be sent along with requests initiated by third party websites.

This means that the session cookie will not be sent when the oauth provider redirects to your oauth callback (.../connect/<provider>/callback) at the end of the Authorization Request.

It means that at that point grant will not be able to get the configuration information as those are stored in the session. As a result grant will redirect you to the root of your site with an Grant: missing session or misconfigured provider error, i.e. https://example.com/?error=Grant%3A%20missing%20session%20or%20misconfigured%20provider.

Using SameSite=Lax which is the default value in modern browsers solve this issue.


I thought everything was finally working until I tried to login from an incognito window.

My app is at flyxc.app Grant is mounted on oauth You can see my config and the express server on github.

So the first time I tried to authenticate, I have:

Request URL: [10ms] https://flyxc.app/oauth/google?x=85&y=24

Request URL: [215ms] https://accounts.google.com/o/oauth2/auth?client_id=754556983658-qscerk4tpsu8mgb1kfcq5gvf8hmqsamn.apps.googleusercontent.com&response_type=code&redirect_uri=https%3A%2F%2Fflyxc.app%2Foauth%2Fgoogle%2Fcallback&scope=openid%20email%20profile&state=72773a896d3aa87c33fe82ba58f25b5988b92966&nonce=247053f8ca6cd2fe238cb64e1de0ff3340395cea&code_challenge_method=S256&code_challenge=blH_5sDhwsE9-ZRoGA2fD12HT1MY-WeNr4GnoP0DiZc

Then it takes some time to enter the email, do the 2 step verification. There are a few more requests during this time:

Request URL: [28.56s] https://accounts.google.com/CheckCookie?hl=en&checkedDomains=youtube&[...]

Request URL: [28.66s] https://accounts.youtube.com/accounts/SetSID?ssdc=1&[...]

Request URL: [28.85s] https://accounts.google.com/signin/oauth/consent?authuser=0&[...]

Request URL: [29.22s] https://flyxc.app/oauth/google/callback?state=72773a896d3aa87c33fe82ba58f25b5988b92966&code=[...]&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=none

At this point grant redirect to "/"

[29.41s] https://flyxc.app/?error=Grant%3A%20missing%20session%20or%20misconfigured%20provider

Question: Why would grant ever want to redirect me to / instead of my callback (/device.html) ?

If I initiate the login again:

Request URL: [5.2m] https://accounts.google.com/o/oauth2/auth?client_id=754556983658-qscerk4tpsu8mgb1kfcq5gvf8hmqsamn.apps.googleusercontent.com&response_type=code&redirect_uri=https%3A%2F%2Fflyxc.app%2Foauth%2Fgoogle%2Fcallback&scope=openid%20email%20profile&state=31c00ed60daa1488cc348763a895c285a0692ed1&nonce=0556040886b7f73a8a16546d83795deeacee1375&code_challenge_method=S256&code_challenge=V3xFedAexGB30MY3ZUFbjdS23ghNds9lXv9h_Rf8ooI

Request URL: [5.2m] https://flyxc.app/oauth/google/callback?state=31c00ed60daa1488cc348763a895c285a0692ed1&code=[...]&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=none

And grant finally logged me in without any error:

Request URL: [5.2m] https://flyxc.app/devices.html, auth ok.

I do see the session cookie created the first time I navigate to the /oauth/google

Do you have any idea of what could wrong when grant receives the code ? Is there a timeout that could cause the issue ?

Otherwise any idea on how I can debug this ? What should I log ?

Thanks !

vicb avatar Oct 09 '20 05:10 vicb

A few thing I have tried:

Use the same express settings as in the example: .use(session({secret: 'grant', saveUninitialized: true, resave: false})) The outcome is the same.

Try to get debug traces by running DEBUG=req,res,json node app/server.js.

There is absolutely no traces on the first attempt.

Seconds attempt looks good:

req POST https://accounts.google.com/o/oauth2/token
    user-agent:     simov/grant/5.4.4
    ...    
form
    grant_type:    authorization_code
    code: ...

res 200 OK
    x-google-esf-cloud-client-params:           backend_service_name: "oauth2.googleapis.com" backend_fully_qualified_method: "google.identity.oauth2.OAuth2Service.GetToken"
     ...
json
    access_token: ...
    expires_in:   3599
    scope:        https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile
    token_type:   Bearer
    id_token: ...
req GET https://openidconnect.googleapis.com/v1/userinfo
    user-agent:    simov/grant/5.4.4
    Host:          openidconnect.googleapis.com
res 200 OK
    x-google-esf-cloud-client-params:           backend_service_name: "openidconnect.googleapis.com" backend_fully_qualified_method: "google.identity.oauth2.openidconnect.v1.OpenIdConnectService.GetUserInfo"
    ...
json
    name:           Victor Berchet
    ...

No traces on the first attempt looks weird. It means that grant does not even tried to get the token.

vicb avatar Oct 09 '20 05:10 vicb

The playground seems to works ok.

It could be either a different config or a different framework... or more likely some error on my side !

vicb avatar Oct 09 '20 06:10 vicb

A few things to note here:

  1. Grant redirects to / by default when it cannot load a provider, see here
  2. DEBUG can log only requests happening on the server, the authorization step is done in the browser

My only assumption right now is that for some reason the session is not being loaded when you get back to the redirect URL /oauth/google/callback.

simov avatar Oct 09 '20 06:10 simov

My only assumption right now is that for some reason the session is not being loaded when you get back to the redirect URL /oauth/google/callback.

Could you please point me to what point in the code I should check that ? I will also try to add DEBUG trace for redis to see if I'm trying to read something.

vicb avatar Oct 09 '20 06:10 vicb

You can put a simple handler before grant:

.use('/oauth/:provider/:callback?', (req, res, next) => {
  console.log(req.session.grant)
  next()
})

This will tell you what's loaded inside the session before entering the Grant handler. If that key is missing or empty, or otherwise the data inside it does not match the :provider from the path, then Grant will redirect you to / with a generic error message:

Grant: missing session or misconfigured provider

simov avatar Oct 09 '20 06:10 simov

Yep there is definitely something fishy with the session:

$ DEBUG=req,express-session,ioredis:* node app/server.js

Before connection:

  express-session no SID sent, generating session +809ms
  express-session no SID sent, generating session +36ms
  express-session no SID sent, generating session +169ms
  express-session no SID sent, generating session +3s

On trying to login

  express-session saving .... +1ms
  ioredis:redis write command[35.224.153.24:18918]: 0 -> set('sess:mDolhG5ItZcx8_...,{"cookie":{"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":true,"path":"/","sameSite":true},"grant":{"provider":"google","dynamic":{"x":"129","y":" ... <REDACTED full-length="855">') +10s

  express-session saving mDolhG5ItZcx8_... +55ms
  ioredis:redis write command[35.224.153.24:18918]: 0 -> set('sess:mDolhG5ItZcx8_...,{"cookie":{"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":true,"path":"/","sameSite":true},"grant":{"provider":"google","dynamic":{"x":"129","y":" ... <REDACTED full-length="855">') +55ms
  express-session split response +1ms
  express-session set-cookie session=s%3AmDolhG5ItZcx8_SBKKyiKPPDxdVILEJ4.mOu045Ju6cB0ncmidmQKs9kWRgCS59jcYy1kt4sAY%2FQ; Path=/; HttpOnly; SameSite=Strict +0ms

When back to google/callback:

  express-session no SID sent, generating session +2m
  express-session saving w_egXuW8_... +1ms
  ioredis:redis write command[35.224.153.24:18918]: 0 -> set([ 'sess:w_egXuW8_...', '{"cookie":{"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":true,"path":"/","sameSite":true},"grant":{}}', 'EX', '86400' ]) +2m

For some reason the session is retrieved (no SID sent) and a new one is generated. I'll debug that more tomorrow.

vicb avatar Oct 09 '20 06:10 vicb

You can try setting up saveUninitialized to true. Another option to play around is the resave one, although that should stay as false in most cases.

simov avatar Oct 09 '20 06:10 simov

I think I found the culprit:

cookie: {
        maxAge: 3600,
        httpOnly: true,
        path: '/',
        sameSite: true,
        secure: USE_SECURE_COOKIES,
      },

It looks like sameSite: true, causes the session cookie not to be attached when google redirect to the callback.

I did a quick test and it seems to be the root cause. I'll confirm later today when I have time to test extensively.

Thanks for your help !

vicb avatar Oct 09 '20 14:10 vicb

I confirm that this solves the problem.

Would you like me to send a PR to add a warning in the examples and the doc ?

vicb avatar Oct 09 '20 16:10 vicb

Thanks for the feedback @vicb, that was really helpful.

I don't think we need to update the examples, as they strive to be short and simple, not necessarily with the best setup for production.

As for the docs, probably additional Cookies section under Misc? We definitely need some info about this somewhere, I'm just not sure yet.

Otherwise we can keep this issue open for now as a reference too, probably with updated title?

simov avatar Oct 10 '20 06:10 simov

I have updated the title and description of the issue.

I think a "FAQ" or "Troubleshooting" section in the docs might be helpful to explain the most common errors people encounters. Explaining why/when "Grant: missing session or misconfigured provider" error is generated would be great. One the the thing this entry should tell to check is the SameSite setting.

vicb avatar Oct 10 '20 15:10 vicb

Same thing happens with sameSite set to none :)

Twiggeh avatar Jan 03 '21 16:01 Twiggeh