top | item 41236745

Tell HN: Google OAuth consent screen issue could be costing you signups

179 points| Aalk4308 | 1 year ago

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.

68 comments

order

mnw21cam|1 year ago

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.

rollcat|1 year ago

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.

afandian|1 year ago

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

WarOnPrivacy|1 year ago

This uBlock Origin custom filter suppresses the popup.

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

extraduder_ire|1 year ago

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.

Suppafly|1 year ago

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

varispeed|1 year ago

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.

shrink|1 year ago

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|1 year ago

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.

Aalk4308|1 year ago

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.

mritchie712|1 year ago

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

xyst|1 year ago

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|1 year ago

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|1 year ago

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.

muzani|1 year ago

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.

kroolik|1 year ago

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|1 year ago

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|1 year ago

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|1 year ago

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

adaml_623|1 year ago

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|1 year ago

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|1 year ago

The server can't reliably detect that.

layer8|1 year ago

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|1 year ago

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.

stephenr|1 year ago

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

xyst|1 year ago

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.

dewey|1 year ago

Login is never "trival" to replace.

uncletammy|1 year ago

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

modeless|1 year ago

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_|1 year ago

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.

GermanJablo|1 year ago

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/

previousjs|1 year ago

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.

Spiwux|1 year ago

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?

yellow_lead|1 year ago

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

Aalk4308|1 year ago

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.

ndriscoll|1 year ago

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.

dangoodmanUT|1 year ago

So they do have junior devs working on their auth screens