[HN Gopher] Tell HN: Google OAuth consent screen issue could be ...
___________________________________________________________________
Tell HN: Google OAuth consent screen issue could be costing you
signups
TL;DR: The "Sign in with Google" form doesn't debounce clicks on
the "Continue" button, so it can trigger multiple redirect
callbacks, in our case causing 15% of signups to fail. We've since
reproduced the issue with several other companies' signup flows.
What's the issue? A few months ago we (Flat.app) noticed that a
meaningful fraction of signups-via-Google to our SaaS product were
failing. I recently found out why, so I'm sharing this PSA for
anyone facing a similar problem. If your app supports "Sign in
with Google", it's likely that new signups will fail if, on
Google's OAuth consent screen, a user clicks "Continue" more than
once. The error will probably be inscrutable to the user, depending
on your setup, so they may just give up instead of trying again. In
our case, we were losing around 15% of signups! I encountered this
issue while investigating failed signups for our product, but I've
also reproduced it with other popular products including ChatGPT,
Doordash, Expedia, and Snyk, so I assume it's widespread. Some of
these products use Auth0, as do we, but others don't, and they
still generate an error of one kind or another. For Auth0
specifically, the error is "You may have pressed the back button,
refreshed during login, opened too many login dialogs, or there is
some issue with cookies, since we couldn't find your session. Try
logging in again from the application and if the problem persists
please contact the administrator." The error tries to be helpful by
listing potential causes, but it fails to mention the actual cause.
That's because Auth0 wasn't aware of this failure mode, based on my
correspondence with them. Why does this happen? After a user
clicks "Continue" the first time, Google will respond with a 302 to
the callback URL, passing along the authorization code and
replaying the OAuth 2.0 state param. If the user clicks "Continue"
again, Google will respond again with a 302 to the callback URL.
The browser will abort the first pending request and issue a new
request to the callback URL. Importantly, the state param is, as it
should be, the same in both requests, and it typically incorporates
a nonce (see https://datatracker.ietf.org/doc/html/draft-ietf-
oauth-security-topics#name-protecting-redirect-based-f). Since the
nonce will already have been consumed by the first request, the
second request will be rejected. Why would a user click "Continue"
twice? Simple: poor UX in Google's consent screen. The "Continue"
button provides almost no feedback that the click was registered,
and the screen provides no feedback that any requests are pending.
So, some users will naturally try again, especially if they're on a
slow connection. What can Google do? Google could disable the
"Continue" button with a loading indicator after the first click.
That would ensure it's not possible to click it twice, and it would
provide an improved UX by showing the user that something is
happening. Google's OAuth consent screen was redesigned at some
point in the past few years. Allowing "Continue" to be clicked
twice, with no visible feedback, may be an unintended regression.
What can I do? Test this in your own application. Be sure to
deauthorize your app before each test run so that you get the OAuth
consent screen. You can do that in your Google account's Data &
privacy section. Also, check your logs for errors to see if you're
losing signups. See if you can detect this scenario and, if so,
provide a better experience to users, e.g., by acknowledging what
caused the error and giving them a clearer path to continue.
Author : Aalk4308
Score : 145 points
Date : 2024-08-13 16:05 UTC (6 hours ago)
| shrink wrote:
| We use Google OAuth to handle hundreds of registrations each day
| and haven't encountered this before. No errors, no customer
| reports. Following your instructions, I logged in to my own
| Google account, removed the connection to our app (via "Third-
| party apps & services") and then did the login again: after
| clicking "continue" the screen changes to "loading" instantly
| before redirecting after a few seconds. There's no ability to
| click "continue" twice. I then tried to sign up to your app
| following your instructions and I can reproduce the issue there:
| the screen doesn't change to "loading" view that I get for our
| app.
|
| Can you share a copy of your OAuth consent screen settings? Maybe
| there's an option influencing this behaviour.
|
| edit: we do not use Auth0, our Google OAuth connection is built
| in house.
|
| edit edit: comparing the URLs, our flow redirects from
| `https://accounts.google.com/signin/oauth/id` to
| `https://accounts.google.com/signin/oauth/consent` after clicking
| "continue" whereas yours remains on
| `https://accounts.google.com/signin/oauth/id` before redirecting
| to your app so there's definitely something different in the
| behaviour.
| Aalk4308 wrote:
| Interesting! It's certainly possible there are additional
| factors at play beyond what I've found to this point.
|
| Curiously, in all other apps I tested and mentioned, I don't
| see the screen changing to "loading" on them. Do you?
|
| Meantime, I'm checking the OAuth consent screen settings to see
| if there's anything relevant.
| shrink wrote:
| According to Google Cloud, our App was created in 2018.
|
| ChatGPT, Retool, Ramp, PostHog and HubSpot have the behaviour
| you've described.
|
| I checked my browser history for `oauth/consent` and found
| the following examples with the loading behaviour:
|
| HelpScout, Google Cloud, Termly.io
| Aalk4308 wrote:
| Very interesting. I just tried in Termly.io as well, and I
| do see the loading indicator you mentioned, albeit not
| _immediately_ (if I throttle the network speed, there is a
| delay before it appears). I captured a HAR file and am
| inspecting it to see if it has anything revealing about why
| the loading screen appears in some cases but not others.
| shrink wrote:
| After watching network requests, I think it's based on the
| use of the Javascript login functionality vs. the redirect
| functionality.
|
| If the "Login with Google" button opens in a new tab and the
| Google OAuth flow completes in the second tab, then the
| process will have the "loading" screen after clicking
| "continue" because "loading" indicates Google OAuth is
| communicating back to the original tab. If the "Login with
| Google" button opens in the same tab, clicking "continue"
| triggers a 302 redirect to your callback URL of which the
| loading speed is controlled by your website.
|
| The immediate workaround is to switch to opening the Google
| OAuth login page in a new window.
|
| edit: "Sign In with Google for Web" appears to be what
| provides the new tab for login functionality https://develope
| rs.google.com/identity/gsi/web/guides/overvi...
|
| edit edit: that's not to say you're wrong, Google should
| definitely fix this but "Sign In with Google for Web" is not
| impacted in case anyone needs an immediate fix for their own
| apps, they can switch to "Sign In with Google for Web" (a
| difference user interface for OAuth).
| modeless wrote:
| The problem with this is it doesn't support OAuth scopes.
| So if you need any Google account permissions beyond a
| simple sign in you can't use it.
| mritchie712 wrote:
| maybe if you throttle your network (e.g. "low end mobile") you
| can hit it?
| Aalk4308 wrote:
| Agreed that there's definitely something different in the
| behavior.
|
| I looked through the HAR files I've captured comparing my
| company's app to Termly. After clicking "Continue", in both
| cases there's a redirect to a URL of the form https://accounts.
| google.com/signin/oauth/consent?as=redacted.... For my
| company's app, hitting that URL results in another redirect to
| my Auth0 tenant, whereas for Termly, hitting that URL results
| in HTML showing the loading indicator (no immediate redirect).
|
| Why the difference? As you said, maybe it's something in the
| OAuth consent screen configuration (though there are no options
| I see that could explain it). Maybe it has to do with the age
| of the account.
| adaml_623 wrote:
| Talk to me about this, "Since the nonce will already have been
| consumed by the first request, the second request will be
| rejected."
|
| What if the nonce was still valid for the second response because
| your server detected that the connection was dropped for the
| first response?
| Aalk4308 wrote:
| That's an interesting possible solution if you're in control of
| the server. If you're using a third-party vendor like Auth0 to
| handle the redirect callback, then of course you're beholden to
| their implementation.
|
| In Auth0's case, it appears the nonce is consumed early in the
| handling of the callback. In my correspondence with them, I
| confirmed that they do see that the first request is aborted
| (in the form of a log), but they take no action as a
| consequence.
| immibis wrote:
| The server can't reliably detect that.
| kroolik wrote:
| Re whys: this can be even simpler. I sometimes catch myself
| rapidly click the mouse button a second time with my finger,
| right after the initial click. This is not intended and may be
| related to low resistance on the button itself.
| 98codes wrote:
| Same, but for me it's because as I've gotten older, sometimes
| my finger gets unsteady enough to double tap something, mostly
| on touchpads rather than a mouse.
| immibis wrote:
| This sounds like an issue that could be solved in your app. I
| hesitate to say it's an issue _with_ your app, because I wouldn
| 't have thought of it either, but you should be able to fix it
| without involving Google, and the fix is simple enough that
| Google can say it's your fault.
|
| You are getting the same callback URL twice, and the second time
| the request is failing. Why not instead, if the user is already
| authorised, let them keep their authorisation and continue by
| redirecting the user to the same place they would go if it was a
| new authorisation? This solution works if you are using a session
| cookie.
|
| If you need to set new cookies upon authorisation, that won't
| work because the browser won't receive the new cookie before the
| second request. Another possibility is to cache the whole request
| and response for a short time in case an identical request is
| received. Since the whole request is identical there will be no
| security issues from nonce reuse.
|
| Alternatively, you could allow nonce reuse within a small time
| window. I'm not familiar with the web stack enough to evaluate
| the security implications of this.
| Aalk4308 wrote:
| Both interesting ideas! I'll pass along in my ongoing chat with
| Auth0. At first thought, the security implications don't seem
| problematic.
| Spiwux wrote:
| I was about to leave a very witty "just be idempotent ;)"
| response but did not consider the nonce. I'd be surprised if
| Google is quick to change this, so I guess be stateful on the
| receiving server, persist that you handled a certain request
| already, and if you get a duplicate request, replay the response
| from the first one?
| ndriscoll wrote:
| It's been a while since I've thought about this stuff, but can
| you put the state param into a cookie (possibly encrypted, but I
| don't think that's necessary)? Assuming you don't use something
| like auth0, you can clear the nonce cookie at the same time that
| you set the login cookie. If they come into your callback without
| a nonce cookie (or with a non-matching one), you can kick them
| back to your landing page (or last saved redirect page) without
| processing the login attempt. If they were already logged in
| because you already processed it, they stay logged in.
|
| The attack the nonce is meant to prevent is tricking someone into
| logging into the wrong account, right? The attacker can't access
| or guess the user's cookie to put the right nonce into the URL,
| so I think that should be safe?
|
| If auth0 were going to implement this in the middle for you, then
| there might be some subtleties to think about with the redirect
| on failure unless you always send the user to a landing page.
| More layers of redirects make this kind of thing hard to think
| through. What's the purpose of auth0 for what you're doing?
|
| This might also not work if the authorization code is not re-
| usable (they're not supposed to be according to the spec) and
| your backend already used it. If it doesn't work, you could again
| kick the user back to a landing page, tell them the IdP had an
| error, and ask them to try again. Or I suppose if you're saving
| the login to a backend session, then it should just work and you
| can ignore the error.
| stephenr wrote:
| It's madness to me that people (not OP specifically) will
| simultaneously say "you have to outsource login to a third party,
| storing passwords safely is too hard" and... also this.
|
| The answer to "what can I do" is "stop depending on a third party
| service that's critical for your business and essentially trivial
| to replace".
| dewey wrote:
| Login is never "trival" to replace.
| ndriscoll wrote:
| IIRC a basic oauth social login implementation is ~40 lines
| of code (assuming you already have things like json parsing).
| You need to understand what you're doing to make sure you do
| the necessary validation (e.g. including the nonce from OP),
| but the part auth0 is doing for you is pretty trivial.
| saurik wrote:
| I agree with you, but I think the comment is about
| replacing Google, not replacing Auth0.
| ndriscoll wrote:
| How so? Google is an IdP. It provides the user's
| identity. Auth0 is a middle layer that (in this case)
| transforms oauth responses into oauth responses.
|
| The thing you'd replace Google with would be something
| like oauth client auto-registration so people can use
| their own oauth server on their domain.
|
| Edit: Oh, I see what you/they mean. That's probably fair,
| but SSO is actually convenient for people, so it's fair
| to offer both oauth and user/password login. And dealing
| with SSO is probably easier than handling passwords,
| reset flows, etc. Passkeys would also work well here if
| they weren't so user-hostile.
| uncletammy wrote:
| There are many reasons one might use a "login with X" flow that
| have nothing to do with storing passwords
| xyst wrote:
| also as a consumer, don't use big tech single sign on. If big
| tech 86s the account for whatever reason, then you lose access
| to the services you linked the account to.
| layer8 wrote:
| Not to excuse the non-debouncing behavior, but I wonder how much
| of those 15% are an actual loss, since it's limited to users who
| are not interested enough to try a second time. I'm not denying
| there's an actual loss, but it may be significantly less than the
| nominal 15%, and it would be interesting by how much.
| klabb3 wrote:
| Theory of mind error here. If it doesn't work I may assume
| their system is down or doesn't support my browser. Thus there
| would be no point in trying again, even if I was interested in
| the product. If it were to work on the second try it'd still be
| a sour taste - bugs are not a good first impression. At least
| personally I would have suspected the SaaS company as opposed
| to Google.
| layer8 wrote:
| If I was truly interested, I would try again in five minutes
| or an hour, or from a different browser/device. Errors are
| caused client-side often enough.
| yellow_lead wrote:
| Google redesigned this screen recently (in the past few months),
| if I remember correctly? I'm not sure if this is related though.
| Aalk4308 wrote:
| They redesigned the sign in / account selection page earlier
| this year, but I haven't been able to find any material
| indicating they changed the OAuth consent screen as part of it
| (such as before/after screenshots), though it's certainly
| possible.
| mnw21cam wrote:
| Just a PSA - I (and probably others) find the "Sign in with
| Google" pop-over extremely annoying. It annoys me both because
| it's over the top of stuff I might want to read, and also because
| it's Google threatening to tell this web site who I am even
| though I have no desire whatsoever to do that. Please hide it
| behind a login button or otherwise only show it when the visitor
| has actually demonstrated a desire to log in specifically using
| Google.
| rrauenza wrote:
| There's a way to disable this! I found this out recently (I've
| only checked with Chrome, but it's a google setting not a
| Chrome one):
|
| https://support.google.com/accounts/thread/219332922/how-to-...
| flutas wrote:
| On Firefox at least that flow says it's only for Android
| devices.
|
| > Allow Google to display a sign-in prompt on Android. Learn
| more about sign-in prompts on Android[0]
|
| with a section below saying Chrome itself has settings as
| well
|
| > To manage third-party sign-ins on Chrome, go to Chrome
| Settings. Learn more about sign-in prompts on Chrome[1]
|
| [0] links to https://support.google.com/accounts/answer/12849
| 458?p=androi...
|
| [1] links to https://support.google.com/accounts/answer/12849
| 458?p=chrome...
| watermelon0 wrote:
| I followed the above link, and it seems that it's a Chrome
| setting now:
| https://support.google.com/chrome/answer/14264742
| cma wrote:
| Will setting that other than default default give the site
| one more bit to fingerprint you?
| afandian wrote:
| Looks like that only works if you're a Google customer?
| varispeed wrote:
| This and I also found that this pop up often sort of crashes
| and is displayed on all tabs and it's not possible to close
| it...
|
| At least that's me on Chrome.
| afandian wrote:
| +1. I find it bizarre that so many web designers values their
| site so low as to sacrifice a quarter of the screen on the off
| chance of a signup. Stop a minute and think of the UX for
| people who aren't Google customers.
| rollcat wrote:
| Staying logged out off Google at all times is slowly becoming
| basic web browsing hygiene. Picture a situation:
|
| - You interact with a bot in a mostly-idle, public chatroom
| (such as !commands on Twitch)
|
| - Bot pastes a url-shortened link that redirects you to a
| Google doc
|
| - Anyone who had that document already open can now link your
| Twitch identity to your Google identity (which may include real
| name+photo)
|
| Granted this particular vector has been open for well over a
| decade, it may just catch you off guard sooner or later.
| WarOnPrivacy wrote:
| This uBlock Origin custom filter suppresses the popup.
| ||accounts.google.com/gsi/*$xhr,script,3p
|
| It's always the 1st one I install.
| Suppafly wrote:
| >Just a PSA - I (and probably others) find the "Sign in with
| Google" pop-over extremely annoying.
|
| So much this, it's 100% and antipattern. It often causes me to
| just close out of page unless the content is actually something
| I really want to see. I have several google accounts associated
| with my gmail, so the box is often kinda big and I'm afraid I'm
| going to accidentally subscribe one of them to something.
| xyst wrote:
| In your "What can I do" section, should add:
|
| "do not add google/apple/facebook(meta)/github sign on in the
| first place"
|
| Not only are we centralizing identity to entities known to
| shutdown accounts for vague reasons. It can introduce painful
| debugging issues, increased support costs, and loss of sales.
|
| Personally, dealt with an issue where a user signed up with "Sign
| in with Apple" but forgot whether they provided Apple associated
| email address or the " @privaterelay.appleid.com"
|
| Also, emails sent to the private relay address would occasionally
| bounce... Very frustrating and time I will never get back lol
| kccqzy wrote:
| The most painful is when a user first logs in using "sign in
| with Google" and then subsequently using "sign in with Apple"
| without understanding that they now have two separate accounts.
| People don't understand that these two accounts are completely
| separate when they see that their Apple ID email is a Gmail, or
| when they have a Google account using their iCloud email.
| ewoodrich wrote:
| As a developer I can sympathize having dealt with frustration
| implementing SSO but as a user (and I'm aware I may be in the
| minority on HN) I've bailed out of trying quite a few webapps
| that don't offer Sign in With Google or Sign in With Apple.
|
| There is a psychological effect where I dread the image of
| whatever half broken bespoke registration flow or inane
| password requirements someone came up with when I only see a
| "Create Account" button.
|
| That may not make a difference for signing up for an account at
| like a bank or something I truly need but for say a Show HN for
| Yet Another Thin Layer Over GPT #372 the odds are high I'll
| just click back and move on with my life.
| maronato wrote:
| It isn't for everyone, but I can't imagine not using
| 1Password + Fastmail's masked emails for registrations
| anymore.
|
| All the privacy of throwaway email addresses with strong
| passwords. Services can store passwords in plaintext and use
| public blockchains as databases for all I care.
|
| Figuring our which service leaked my email and blocking all
| messages from it is one click away.
| modeless wrote:
| I know they call it a nonce but how important is it to invalidate
| it instantly on first use? It's important for it to be
| unguessable, of course. But what security property does the
| invalidation serve? If an attacker can get the nonce they can
| just as easily get the access tokens after authentication, can't
| they?
| loa_in_ wrote:
| The nonce is unique to the request-reply pair. Holding onto it
| beyond first use is useless, so it's discarded. If not that it
| would have to be time based otherwise (time-to-live) because
| server can't hold on to infinite of those. And if the time is
| too short it will cause problems, if it's too long it'll cause
| problems. It just always causes problems.
|
| > If an attacker can get the nonce they can just as easily get
| the access tokens after authentication, can't they?
|
| That's not the case. A js injection would usually lead to read
| access on the current page but not the next one.
| modeless wrote:
| It's my understanding that the OAuth "state" parameter nonce
| is generated and stored and validated on the client, not the
| server.
| loa_in_ wrote:
| You're right. sorry. There goes me not refreshing my memory
| about the flow.
| dangoodmanUT wrote:
| So they do have junior devs working on their auth screens
___________________________________________________________________
(page generated 2024-08-13 23:01 UTC)