Sign-in · explained simply

How Google One Tap works

You open example.com while signed in to Google and tap Continue as Alex. Google hands the site a sealed letter about you, and your server checks the seal before it signs you in.

gsi/client→ prompt()→ Continue→ ID token→ credential→ verify→ sub
example.com prompt() gsi/client Alex Continue FedCM card
1

Your page asks, and the browser draws the card

Your page loads Google's script and asks for an introduction. It draws nothing itself. The browser shows the card, with the Google account it already knows.

You call google.accounts.id.initialize() with your client ID, then prompt(). With FedCM, the browser puts the card on top of the page and sets its size and place. The old use_fedcm_for_prompt flag is now ignored. The card shows only on HTTPS, and only when the browser has an active Google session.

<script src="https://accounts.google.com/gsi/client" async></script>
<script>
window.onload = () => {
  google.accounts.id.initialize({ client_id: "YOUR_CLIENT_ID",
    callback: (r) => sendToServer(r.credential) });
  google.accounts.id.prompt();
};
</script>
card = One Tap prompt browser draws it = FedCM
Continue you tap ID token iss accounts… aud CLIENT_ID sub 1100… email alex@… signed JWT
2

One tap, and Google writes a sealed letter for this site

You tap Continue. Google writes a short letter: who wrote it, who it is for, and who you are. Then it seals the letter, so nobody can change a word.

The letter is an ID token, a JWT signed by Google. iss names the writer. aud holds your client ID, so a letter written for another site fails your check.

letter = ID token (JWT) wax seal = signature letterhead = iss addressee = aud
your page POST credential your server
3

Your page passes the letter to your server, still sealed

Your page does not open the letter or trust it. It sends the whole letter to your server, and the server does the checking.

The token arrives as credential. Pick one of two ways to deliver it. You cannot set both.

callback · your JavaScript

Your function gets a CredentialResponse. Send its credential to your server, for example with fetch.

login_uri · a form post

Your login endpoint gets a POST with credential and g_csrf_token. Reject it unless that token matches the cookie of the same name.

ID token aud CLIENT_ID iss accounts… exp > now match? v3/certs key Google's keys
4

Your server checks the seal before it reads a word

Anyone can write "from Google" on a letter. Your server compares the seal with Google's public keys. Then it reads the addressee, the writer and the use-by date.

The keys live at https://www.googleapis.com/oauth2/v3/certs and rotate, so cache them only as long as Cache-Control says. Accept the token only when aud is your client ID, iss is accounts.google.com or https://accounts.google.com, and exp has not passed.

from google.oauth2 import id_token
from google.auth.transport import requests

# checks the seal, aud, iss and exp; raises if one fails
info = id_token.verify_oauth2_token(
    credential, requests.Request(), CLIENT_ID)
user_id = info["sub"]

Always pass your client ID. Without it, the library skips the aud check.

seal sample = Google's public keys use-by date = exp

Reading the letter is not checking it.

sub 1100… alex@a.com sub 1100… alex@b.com sub 1100… sub 1187… sub 1093…
5

File the person under sub, never under the email

Every letter carries a member number. People change their email. The member number stays with the Google Account for good.

Google says sub is unique among all Google Accounts and never reused. The email claim can change over time. Use the email only to find an account that already exists, and only when email_verified is true.

Keyed by email

  • Alex changes email.
  • The next sign-in makes a second, empty account.

Keyed by sub

  • Alex changes email.
  • Both letters open the same account.
member number = sub drawer = your user row

The email is a label. The sub is the person.

Before you rely on it