Read Fundamentals first if you haven’t.
Generate it
What you get
What you get
Three manifest keys do all the work
module.json
string
required
Where the button sends people. Must match your actual route exactly.
string
required
The
users column holding the external account ID. Also how “is this linked?” gets answered.string
required
Path under
public/.object
Button branding:
bg, text, hover. Falls back to a built-in table by provider name.OAuthProviderRegistry reads them, and the login page, market checkout and profile page all get their
provider list from it.
Install a Socialite driver
Most providers are on socialiteproviders.com. Google, GitHub, Facebook, X, LinkedIn, GitLab and Bitbucket ship withlaravel/socialite already.
Then uncomment two lines in your service provider:
For a driver built into
laravel/socialite, leave both out.Credentials come from the database
Admins configure OAuth apps in the UI, not in.env, so you pass config per request:
driver() helper that does this and returns null when the module
isn’t configured yet.
The callback has three cases
The generated controller handles all three. Worth knowing what each one is.- Already signed in
- Known external ID
- Brand new
Link the account, but refuse if that external ID belongs to someone else:
The one rule you shouldn’t relax
registerViaOAuth() resolves in this order:
1
Match the external ID
Found, so refresh the avatar and sign them in.
2
Match the email, but only if that account already has this provider's ID
Same result.
3
No match, so create the user
Fires
Registered and sends the welcome notification.An account with this email already exists but is not linked to Acme. Please log in with your password first, then link your Acme account from your profile settings.Your catch block should surface that message rather than a generic failure:
The generator writes this for you. The exact phrasing is what the check matches on, so don’t reword
it.
Unlinking
Provide the route. The generated controller already refuses to unlink someone’s only way back in:Test it
Fresh sign-up creates the account and sends exactly one welcome email
Sign out, sign back in: same account, not a duplicate
A password account can link from the profile page and shows as linked
Linking the same external account to a second user is rejected
Unlink clears the column and the button says “Link” again
Disabling the module removes the button from login and profile
An email matching an unlinked account gives the actionable message, not “Authentication failed”
Worth reading
GoogleProvider
The minimal version.
GithubProvider
Same shape, different driver.
DiscordProvider
What this can grow into: role sync and 17 listeners sending DMs.

