grant
grant copied to clipboard
SameSite=Strict cookies break the oauth flow (Grant: missing session or misconfigured provider)
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 !
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.
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 !
A few things to note here:
- Grant redirects to
/by default when it cannot load a provider, see here - 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.
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.
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
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.
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.
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 !
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 ?
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?
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.
Same thing happens with sameSite set to none :)