Skip to main content
Social sign-in is two calls, because the redirect leaves our process: start hands you the URL to send the browser to, callback spends the code the provider sent back. Both are hosted-mode endpoints reached with a publishable key, from your own domain. Nothing rides a cookie: the state comes back to you and presenting it at the callback is what proves the two halves are one flow.
Google today. Adding a provider is a row and an implementation on our side — never a migration, and never a change to the two calls below.

The OAuth application is yours

You register the application in Google’s console and store the credentials with us. The consent screen your customer reads then says your product’s name, not ours — an identity provider whose own brand appears on somebody else’s login screen is selling the wrong thing.
The secret is encrypted at rest and never returned. You pasted it from a console you administer; a secret readable back out of the panel is a secret with one more way out. Pasting it again is how you rotate it. Per environment, so you can point test at a throwaway Google project and ship with the real one.
A test environment with nothing configured may fall back to a shared UserKit-owned application, so social sign-in works in your first ten minutes. Live never does: that consent screen has to be yours.
Your redirect URI must be registered with Google as usual. It also has to pass our own gate — its origin must be in the publishable key’s allowed-origins list whenever that list is set. Your login screen does not have to be told any of this. GET /v1/config/{key} answers with the providers this environment can actually sign someone in with, so the button appears when you register the application and not when you next deploy. See Auth modes.

Start

200
Send the browser to authorization_url and keep state where the callback page can read it — session storage is the usual answer. The flow is good for fifteen minutes. PKCE is applied on every flow, and the verifier never leaves our process: the authorization code travels through a page we do not control, so a stolen code alone buys nothing. Passing anonymous_id folds the visitor this person already was into the account, attribution included. Rate limited to 30 per hour per IP.

Callback

Your redirect page receives ?code=… and posts it back with the state it kept:
200
The same shape as a password sign-in, so your SDK has one response to handle however somebody arrived. Authenticating at the provider proves the account, so the session is verified. The state is single use and bound to its provider and its environment. Unknown, expired, already spent, and “the provider refused the code” all answer the same 401 invalid_grant. A provider we could not reach answers 502 provider_unavailable — that one is not your caller’s fault. Rate limited to 30 per hour per IP.

How the account is resolved

This is the part worth reading twice. In order:
1

A contact already linked to this provider account

Resolved by the provider’s stable subject id, never by the email. Somebody who changes their Google address is still the same person.
2

The provider vouched for an address

It links onto the contact that owns that address, or creates one. Two parties have now proved the same address, which is what makes linking permissible at all.
3

The provider did not vouch

The provider account is the only identity the sign-in carries. The email lands as an attribute and resolves to nothing — a new contact, even if the address looks familiar.
Step two has a case underneath it. If the contact that owns the address had never proven it — signed up with a password and never clicked the verification link — then the password on it was never proven either. It is removed, along with every session and every outstanding one-time token, and the inbox is told what happened.That is the classic takeover closed: sign up with somebody else’s address, wait for them to arrive by Google, and inherit their account. Here, the unproven credential does not survive into the account whose owner just took possession of it.
Linking is never silent. When a social account lands on a contact that already existed, a notice goes to the address — only the person holding that inbox can say whether it was them.

Removing a provider

Existing links survive. They are history, and a developer who registers again tomorrow should not find their customers locked out.