Hacker News new | past | comments | ask | show | jobs | submit login
Tell HN: Google OAuth consent screen issue could be costing you signups
179 points by Aalk4308 3 months ago | hide | past | favorite | 68 comments
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.




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.


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.


How would they be able to link that? Document owners can't see non-domain users who viewed a particular document (and even for domain users, that can be disabled).


My recollection is that, at the top-right corner of the screen, you can see the email addresses of the other people who are currently editing the document (but not those who did in the past). If they aren't logged in, you'll see them as names like 'Anonymous Aardvark' instead.


That is inaccurate. You only see individuals with whom you have directly shared the document with and/or people in your domain (if a business/enterprise customer). For anything link shared, you see the anonymous animal names.


+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.


It's probably not the designers. In my experience, the pushback is something like

"I know it doesn't make sense, but the VP wants this so just make it happen"


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-...


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/12849458?p=androi...

[1] links to https://support.google.com/accounts/answer/12849458?p=chrome...


I followed the above link, and it seems that it's a Chrome setting now: https://support.google.com/chrome/answer/14264742


Will setting that other than default default give the site one more bit to fingerprint you?


Looks like that only works if you’re a Google customer?


This uBlock Origin custom filter suppresses the popup.

    ||accounts.google.com/gsi/*$xhr,script,3p
It's always the 1st one I install.


If you don't use uBlock Origin, you can also pihole or otherwise block the domain accounts.google.com. This is surprisingly non-destructive to active Google sessions, though you can't create new ones with it blocked.


Go to

chrome://settings/content/federatedIdentityApi

enable: Block sign-in prompts from identity services

See: https://issues.chromium.org/issues/343584523


It's especially annoying how it shows your profile pic, name, and email address on screen.

I've seen the gmail addresses of so many twitch streamers who were capturing their screen without knowing that would happen. Most of them with addresses they'd rather not be public.


>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.


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.


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.


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.


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.


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://developers.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).


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.


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


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.


maybe if you throttle your network (e.g. "low end mobile") you can hit it?


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


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.


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.


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.


For me it is the exact other way around. If you don't have any other sign up system, I will bail immediately. I have a Google account, but it is "empty" and only used for settings and Android phones.

I don't want Google to know which services I am using and I had accounts shut down by big tech for non-specified reasons, probably because I didn't want to share my phone number.

Same issues could materialize that we have with Chrome now. I don't want to watch an ad when I log in to something.


It's pragmatic if you're making (Android) apps, in which your payments are already tied into it, and you're already at Google's whims.

But websites shouldn't copy the ideal UI for Android and so on.


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.


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.


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.


Both interesting ideas! I'll pass along in my ongoing chat with Auth0. At first thought, the security implications don't seem problematic.


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?


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.


The server can't reliably detect that.


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.


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.


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.


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".


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.


Login is never "trival" to replace.


If handling a user identifier and a hashed password securely, and providing a means to reset said password is more than "trivial" relative to the rest of a business application, the whole scenario is moot anyway because it's going to be shut down before anyone who might attack it is even aware it existed: there isn't really a profitable market for hello-world and "my first todo" apps.


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.


I agree with you, but I think the comment is about replacing Google, not replacing Auth0.


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.


> dealing with SSO is probably easier than handling passwords, reset flows, etc.

It's really not that difficult. Yes you need to be aware of risks, and be more careful with the data. But it's not exactly rocket science, and you're never going to end up in a scenario where your users can't login because the login flow is out of your hands, as per the topic of this thread.


> you're never going to end up in a scenario where your users can't login because the login flow is out of your hands

It's more likely that the person re-implementing the SSO flow is making a mistake and the login behaviour is getting messed up than the default Google/Apple SSO implementation that's deployed to billions of users and business critical for many companies that use that kind of SSO internally too.


> It's more likely that the person re-implementing the SSO flow is making a mistake

The thread has other people confirming the same behaviour - and the description of the issue (preventing a double-submit of a non-idempotent action) is something most web-focused developers learn in their first year.

I don't understand why people assume that the people working at Google/etc are inherently incapable of producing bugs. Go literally invented a new programming language to make their staff less likely to fuck up because they don't have much real world experience: https://www.youtube.com/watch?v=uwajp0g-bY4


Taking HN thread replies or users as a sample for anything is almost never a good idea.

Even in this thread there's a split between people who can replicate it and people who can't, so it's more likely an edge case and not a global issue affecting all Google SSO implementations.


> Even in this thread there's a split between people who can replicate it and people who can't

I don't think "even the buggy behaviour is not reliably reproducible" is really the selling point you should be touting, if your claim is that Google "does it better".

I can't reply below yours, so I'll add this:

> The point is that globally rolled out solution that billions of users use every day and companies depend on is obviously better tested, better monitored, fixed quicker, more accessible than a one-off handcrafted solution for most use cases and companies where that is not their core competency.

None of those things are obvious, and the original topic of this is from the context of a software developer, that also offers their own direct "email + password" sign in option, so clearly the monumental task of storing a password, and offering password resets is not too much for them.

Your claim that large companies do things "better, obviously" is honestly laughable.


You are missing the point.

The point is that globally rolled out solution that billions of users use every day and companies depend on is obviously better tested, better monitored, fixed quicker, more accessible than a one-off handcrafted solution for most use cases and companies where that is not their core competency.


There are many reasons one might use a "login with X" flow that have nothing to do with storing passwords


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?


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.


It's my understanding that the OAuth "state" parameter nonce is generated and stored and validated on the client, not the server.


You're right. sorry. There goes me not refreshing my memory about the flow.


I use authjs (aka next-auth) and recently documented that Google login requests additional access every time the user logs in[1].

I created an issue with a repro, although the maintainers moved it to discussions. It seems that the problem is with Google, but in that case I don't understand why the next-auth example works fine[2].

There are other users affected. If anyone knows how to solve this problem, I would appreciate it if you could say so.

https://github.com/nextauthjs/next-auth/discussions/11160 https://next-auth-example.vercel.app/


These kinds of Next issues are why I stopped using it.

It is not bugs per-se as every framework has them.

It is that you are utterly stuck when they do and even 100s of thumbs up on issues don't get things fixed.

Case in point: can't defer chunk scripts anymore. Hard to get good performance scores as this is untenable.

Fetch cache limit is 2Mb or something like that and enforced by Verel. You need to fork NextJS and self host it to fix.

Probably 2 or 3 other such issues I forgot as well

Any over opinionated "batteries welded" frameworks will have this issue.

Use any of the usual MVC suspects like Rails for example and you avoid being stuck. You can find fixes or swap libraries. They tend to have battle tested out such issues anyway.


Tangential, major sites had/have broken "login with Google" on Firefox. Apparently devs have to jump through hoops or change to popups which may not work with mobile.

https://firebase.google.com/docs/auth/web/redirect-best-prac...


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?


Google redesigned this screen recently (in the past few months), if I remember correctly? I'm not sure if this is related though.


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.


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.


So they do have junior devs working on their auth screens




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: