Skip to main content
This is the smallest module type.
Read Fundamentals first if you haven’t.

Generate it

You also need a logo, or the login button will not render:

Three manifest keys do all the work

module.json
Where the button sends people. Must match your actual route exactly.
The users column holding the external account ID. Also how “is this linked?” gets answered.
Path under public/.
object
Button branding: bg, text, hover. Falls back to a built-in table by provider name.
A provider missing any of the first three gets skipped in silence, so the button just never shows up. module:doctor reports it as an error.
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 with laravel/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:
The generated controller has a 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.
Link the account, but refuse if that external ID belongs to someone else:
Skip that check and one external account can be attached to several DezerX users, which is an account-takeover path.

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.
If the email matches an account that was never linked to this provider, it throws:
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.
That looks unhelpful and it’s deliberate. Auto-linking on email alone means anyone who can set that address at the provider takes over the DezerX account, and not every provider verifies email addresses.Discord and GitHub always refused. Google used to auto-link, and no longer does.
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:
Fire an event if other modules should react. DiscordProvider removes Discord roles on unlink.

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.
If your provider has a notification channel or a roles concept, DiscordProvider is the pattern to copy. It’s just core events, covered in Fundamentals.