One click to sign in, three rules to keep it safe: social login for vibe projects
Social login cuts signup friction to one click — but its real costs hide in account merging, state and PKCE, and token storage. This guide covers OIDC vs OAuth, provider choice, merge rules, and the pre-launch test list.

Users do not refuse to sign up — they refuse to memorize another password
Many vibe projects lose signups in a very quiet place: a visitor opens the registration page, sees "set a password, go to your email, click the verification link, come back and log in," and closes the tab. No error, no complaint — just a visitor who never returns. Social login (one-click sign-in with Google, GitHub, Apple, or another existing account) attacks exactly this step: it reduces "create a new identity" to "click once, confirm once."
But social login is not "add three buttons" and done. Done wrong, it creates a different kind of trouble: one person ends up with two accounts, a changed email locks them out of the old account, permissions get over-requested, or a hijacked callback lets an attacker in. This guide covers how to wire social login correctly: first, what OAuth and OIDC each do; then which providers to pick, how accounts merge, the security checklist, and the tests to run before launch.
The example product "Pinecone List" (a fictional to-do app) and the user "Lin Xiao" are fictional, used only to illustrate flows. Specific interface parameters are subject to each provider's current official documentation.
Two concepts first: OAuth is authorization, OIDC is authentication
The most common misunderstanding is treating OAuth 2.0 as a "login protocol." Strictly, OAuth 2.0 solves authorization: the user agrees to let your app act on their behalf to access something, like reading their public profile. It does not by itself tell you who the user is — you get an access token, not a proof of identity.
OpenID Connect (OIDC) is the authentication layer built on OAuth 2.0. It adds an ID Token: a JWT signed by the identity provider, carrying the user's stable identifier (sub), email, and whether the email is verified. After your app verifies the signature, issuer, and audience, it can say "this person is the user behind this sub." So the correct description is "log in with OIDC," not "log in with OAuth." This is not pedantry: an app that treats an access token as a login credential equates "can call an API" with "is this person," and that confusion is where identity bugs start.
The practical conclusion for vibe projects: prefer providers that support OIDC, and use the ID Token's sub as the user's stable key under that provider — never email or nickname, which can change. Emails get reassigned, nicknames get edited; sub stays stable within that provider.
Which providers: cover 80% of users first, then the long tail
Choose providers by your users, not by putting every button on the market on your page. For a globally oriented developer tool, Google and GitHub usually cover the vast majority: Google for general users, GitHub for developers — and GitHub sign-in carries a natural signal that "this person is probably a developer," which fits developer tools well. Add Apple when your audience skews toward the Apple ecosystem; Apple also has additional requirements in some App Store scenarios, so check the current review rules before submission.
Each additional provider multiplies maintenance: callback URLs, key rotation, brand rules, and support tickets ("I signed in with X, why can't I find my account?"). My recommendation: launch with two providers, watch the signup source distribution for a month, then decide on a third. Button order matters too: put the highest-converting provider first, not in alphabetical order. And always keep email sign-in available — social login is "a more convenient road," not "the only road." If a provider has an outage, your users should not be locked out entirely.
Also plan for "the user has no account with that provider." If your product serves enterprise users whose companies block personal Google accounts for work tools, social-only login strands them. Keeping email plus password (or email code, or passkey) as a fallback is not old-fashioned — it is leaving every user a way through the door.
The hardest part is not connecting — it is account merging
The most common support ticket after social login launches is not "it doesn't connect," but "I have two accounts." The classic scenario: Lin Xiao registers Pinecone List with email, later clicks "Sign in with Google," and the system creates a second, empty account. The user sees "my data is gone"; the system sees "two legitimate accounts."
The core rule: your own user table is the identity; a provider account is just a sign-in method attached to it. In the data model, one user can hold multiple identities, each recording the provider, the sub under that provider, and when it was linked. On sign-in, look the user up by identity first; if no identity matches, decide by rule whether to create a new user or guide a merge.
Merge timing and method need care by case. The safest approach: when the social login's email matches an existing account's email and that email is verified on the provider side, prompt "we found an existing account — merge?" and require the user to first verify through the existing account (enter its password, or click an email verification link) before merging. Never auto-merge on "same email" alone — same email does not mean the same person, especially with recycled corporate mailboxes. A wrong auto-merge means A can enter B's account, which is far worse than a "duplicate account" ticket.
Unlinking needs the mirror rule: a user removing a social login must not be able to remove their last sign-in method, or the account becomes permanently inaccessible. Before unlinking, check the user still has at least one working sign-in method (another social link, email password, or passkey); if not, guide them to add one first. The same "will this lock the user out?" check applies to email changes and account deletion.
Security checklist: state, PKCE, callback validation — all required
Most social login security problems are not cryptography problems but flow implementation problems. The following items must each be checked before launch.
First, the state parameter must be random, single-use, and bound to the current session. It prevents cross-site request forgery: an attacker handing their own authorization code to a victim so the victim's account gets linked to the attacker's social account. Generate a random state, store it in the session (or an encrypted cookie), compare it on callback, and reject on mismatch. Never use a fixed string or a predictable timestamp as state.
Second, use PKCE (pronounced "pixy"), especially for mobile and single-page apps. PKCE works like this: before authorization, generate a random code verifier and send only its hash (the code challenge) to the provider; when exchanging the code for tokens, present the original verifier. Even if the authorization code is intercepted in transit, it cannot be exchanged without the verifier. For server-side apps, PKCE is still worth enabling — it is one of the cheapest layers of defense in depth.
Third, callback URLs must be registered exactly in the provider console, and the server must validate the callback, enforce single use of codes, and exchange tokens server-to-server. Authorization codes, access tokens, and refresh tokens must never appear in frontend URLs, logs, or error messages. Logs should record only "a user completed sign-in with provider X at time T" — logs without tokens are the safe kind of logs.
Fourth, ID Tokens must be signature-checked, with issuer, audience, and expiry verified. Do not trust a JWT's payload just because you parsed it — an unverified JWT is no different from plain JSON. Fetch the issuer's public keys from its published JWKS endpoint and cache them; do not hard-code a snapshot of keys into your code. And read the email-verified flag: only treat the email as a trusted contact channel when the provider says it is verified; otherwise it is display information only.
Minimum permissions: only request the scopes you actually need
Every social login shows the user a consent screen listing what your app wants. The more scopes you request, the more likely users decline — and the larger the blast radius if something goes wrong. The rule: sign-in only needs to identify the person, which usually means the basic openid, profile, and email scopes. Do not grab "read user repositories," "read contacts," or "post on their behalf" at sign-in time.
The right pattern for extra permissions is progressive authorization: when the user first uses a feature that needs a permission, request it then, and explain in the interface why it is needed and what happens without it. If Pinecone List wants to turn a GitHub issue into a to-do with one click, request repository read access the first time the user clicks that feature. Two benefits: signup conversion is not dragged down, and users understand and consent more easily when they have context.
Review the scope list regularly: permissions added during iteration but no longer used should be removed promptly, and you should walk through the provider-side consent screen yourself from time to time to see what users actually see. Many teams' scope lists only ever grow; three years later they discover the sign-in page is asking for a pile of permissions nobody uses.
How to store tokens, and when to revoke them
After sign-in succeeds, your own session is a separate matter from the provider's tokens. The recommended setup: maintain login state with your own session mechanism (cookie session or your own token), not by treating the provider's access token as your session credential. When you need to call the provider's API, exchange a stored refresh token — and refresh tokens must be stored encrypted, never in plaintext in the database.
Revocation and expiry need a plan. When a user clicks "log out" in your product, clearing your own session is enough; you generally do not need to revoke the provider-side grant. But when a user clicks "disconnect this social account," you should also revoke the grant on the provider side (if the provider offers a revocation endpoint) — otherwise your database says unlinked while the provider still considers the grant valid. Likewise, account deletion should clean up all linked social grants, with an audit record of who disconnected which provider, and when.
One more scenario that gets overlooked: what happens when the user deletes their provider-side account, or their email gets recycled? The safe behavior: the next time that identity signs in, if the provider reports the sub no longer exists or the email has changed, route through a re-verification flow instead of silently reusing the old binding. Treating "the provider says this person still exists" as permanently true is the root of many account-takeover problems.
Build it yourself or use an auth service: decide by team size
At this point many small teams wonder: build social login yourself, or use a hosted identity service (Auth0, Clerk, Supabase Auth, and similar)? The answer depends on team size and account-system complexity. If you have one product, two or three providers, and your own user table and session mechanism already, building it yourself is not hard: the core is the authorization redirect, callback validation, and identity linking — three pieces of logic, controllable code volume, and full control over account data.
But if multiple products must share sign-in, or you need enterprise SSO, multi-tenancy, or fine-grained auditing, a hosted service earns its keep: it absorbs provider differences, key rotation, and compliance details, and you pay a subscription to save days of edge-case debugging. A practical test: when you find yourself spending a whole day on "the third provider's callback acting up again," it is time to consider a hosted service. Conversely, if your account system is simple and you want user data to pass through as few third parties as possible, keep building it yourself. Neither route is superior — only the cost structure differs.
One underestimated cost: brand and compliance details. Each provider has brand rules for sign-in buttons — wording, logo usage, colors. For example, buttons typically must read "Sign in with Google" rather than a casual paraphrase, and logos must not be recolored or outlined. These are not optional decoration: violating brand rules can get an app review rejected, and in serious cases an OAuth client suspended. Before launch, read each provider's brand guide, and archive screenshots of your buttons. It is half an hour of work that prevents large trouble.
Privacy policy and data-use statements must be updated in step: state clearly which fields social sign-in collects (usually avatar, nickname, email), what they are used for, how long they are kept, and how users delete them. Many teams wire up social login but forget the privacy policy, creating legal risk and trust risk at once. What users see on the consent screen should match what your privacy policy says you collect; any gap is a future support and compliance ticket.
Pre-launch tests you must run
Social login testing cannot cover only the happy path (click, authorize, land on the home page). Run at least these cases before launch. First, a new user registers once per provider, confirming profile data (avatar, nickname, email) fills correctly with no duplicate accounts. Second, an existing user signs in socially where the email matches an existing account: the merge guidance flow runs, both sign-in methods reach the same account afterwards, and no data is lost. Third, declining authorization, closing the window mid-flow, and callback timeouts must all return to the sign-in page with a readable message — not a blank screen or a 500.
Fourth, negative security tests: forged state callbacks, replayed authorization codes, and tampered ID Token payloads without signature verification must all be rejected. Fifth, multiple devices and browsers: mobile browsers, in-app WebViews, and desktop browsers each once, because callback and cookie behavior differs across them. Sixth, offline and slow networks: when the provider API times out, the sign-in page must say "sign-in service temporarily unavailable, please try again later," not spin forever.
After launch, watch three metrics: signup conversion per provider, next-day retention of social sign-in users (compared with email signups), and the volume of "merge my accounts" support tickets. If one provider converts noticeably worse than others, look at its consent screen first — most likely the scopes are over-requested or the branding is off, not that users dislike that provider. Wiring social login up is only the start; watching the data and tuning is where it actually pays off.
To sum up: social login's value is cutting signup friction to a minimum, but its costs hide in account merging, security flow details, and long-term maintenance. Set three rules before you build: your own user table is authoritative, merges require verification through the existing account, and permissions stay minimal. With those three in place, social login keeps users; without them, it manufactures support tickets.
Comments (0)
Related articles

Signup anti-abuse is a layered sieve, not a wall. This guide covers bot types, the friction ladder, verification trade-offs, new-account limits, and cleanup.

Merging looks like turning two accounts into one, but it touches subscriptions, balances, content, permissions, third-party bindings and audit trails, and a bad merge is almost impossible to undo. This guide covers the judgment, ownership checks, conflict matrix, execution order and rollback.

Email verification and password recovery are two locks on the same door: one proves an address is yours, the other proves you can get back in after losing the key. This guide covers link vs code, token rules, session handling, rate limits, special cases, and a 15-scenario launch checklist.