Social auth
This pack adds social sign-in to your API. Because OAuth rides a full-page browser redirect, the backend runs the provider round-trip and hands the result back to your frontend, fitting the guard your app already authenticates with. It ships endpoints, not screens.
- Fits your existing guard, a session guard's cookie rides the redirect, an access-token guard hands the frontend a one-time PKCE-bound code to exchange for a token.
- Provider emails are trusted only when the provider verifies them, and an unverified one is routed through your existing email-verification flow.
- An email that already belongs to a local account is refused rather than silently linked to the provider.
- A password-less account cannot disconnect its last provider, so it can never lock itself out.
- A methods endpoint reports the configured providers and the visitor's last-used one, so the client renders the right buttons without hardcoding them.
This pack extends the auth pack's User model and reuses its email-verification flow. Apply the auth pack first.
Apply the social auth pack with Flow. Start a new session in your selected coding agent and execute the following slash command inside it.
After applying, Flow will make the following changes to your app.
-
Discover your default guard, access tokens or session, and the frontend URL your OAuth callback redirects to.
-
Land the backend as shipped, from the
social_identitiesmigration and the nullable-password change through thewithSocialIdentity()model mixin, the guard-branched OAuth redirect and callback controller, the public methods endpoint, and the connected-accounts endpoints. On a tokens guard it adds the PKCE token-exchange endpoint. -
Configure the providers you pick, Google and GitHub by default, record the last-used method on your existing login and signup, and add the two-factor branch when your app already has two-factor.
-
Run the tests, drive the endpoints, and hand you a brief for the frontend to build against.
Apply the social auth pack to your API by hand by working through the steps below in order. It builds on the auth pack, adding provider sign-in as JSON endpoints that fit the guard your app already authenticates with, and hands off a brief for your frontend at the end.
-
Confirm your auth foundation
This pack builds on the auth pack. Make sure your API already has it before continuing.
- Your User model composes
withAuthFinder(...)andwithManagedEmail(), and@adonisplus/personais installed. - The email-verification flow is wired: the
account_activation_provisionmailer, resolvableemail_verificationsroutes, andunverifiedEmailandcreateEmailVerificationTokenon the User model. - The authenticated
accountgroup, undermiddleware.auth(), is registered. The connected-accounts routes join it.
- Your User model composes
Flow adapts the pack to your app, so the exact set of created and edited files depends on what you already have and which guard you authenticate with. This is the shape of an apply onto the API starter kit with the auth pack in place.
Configuration
Social identities are stored by the withSocialIdentity() mixin on the User model, which reads and writes one row per linked provider account. Pass an options object to the mixin to change where those rows live.
table
The table social identities are read from and written to. Defaults to social_identities.
export default class User extends compose(
UserSchema,
withAuthFinder(hash),
withManagedEmail(),
withSocialIdentity({ table: 'oauth_identities' })
) {}
One provider account links to one user
Every linked account is a row keyed by its provider name and the provider's own user id, with a unique constraint on that [provider, provider_user_id] pair behind it. Two behaviors follow:
- Linking a provider account the user already holds just refreshes it in place, so re-authenticating with the same provider is safe to repeat.
- Linking one that already belongs to a different user is refused with an error, so a single Google or GitHub account can never attach to two local accounts.
Provider data is a snapshot, not your source of truth
Each identity row keeps the provider's email, name, nickname, and avatarUrl, and every sign-in refreshes them from the provider. These fields stay on the identity row and never flow back to your users table.
So a user renaming themselves on GitHub does not rename their account with you. The connected-accounts view reads the fresh provider snapshot, while your account record stays under your app's control.
Provider emails are trusted only when verified
Not every provider confirms that the person owns the email it hands back. Ally reports this per provider as emailVerificationState, one of verified, unverified, or unsupported, and a new signup forks on it:
- A
verifiedemail is treated as active immediately. - Any other state routes the new account through the same email-verification flow the auth pack ships, so an unconfirmed address still has to prove itself before the account is active.
A colliding email is refused, not merged
When someone signs in with a provider whose email already belongs to a local account that has no link to that provider, resolveSocialIdentity returns a link_required outcome and the pack refuses the sign-in. It neither logs them in nor attaches the provider to the existing account.
Auto-linking on a matching email would let anyone who controls an address at a provider take over the local account that happens to share it. Instead the user is told to sign in with their original method and connect the provider from their account settings, where the link is made against a session that is already theirs.
1.0.0
Initial release.