audunru/social-accounts
Laravel package for connecting and managing social media accounts. Link providers like Facebook, Twitter, and Instagram to your user models, handle OAuth authentication, and store access tokens securely so you can publish or fetch data on behalf of your users.
1. Install and scaffold
composer require audunru/social-accounts
php artisan vendor:publish --tag="social-accounts-config"
php artisan vendor:publish --tag="social-accounts-migrations"
php artisan migrate
Two migrations are published: create_social_accounts_table and make_email_and_password_nullable (the second exists so Socialite-only users can be created without a password). If you already have users, review the second one before running it.
2. Trait on the owner model
use Audunru\SocialAccounts\Traits\HasSocialAccounts;
class User extends Authenticatable
{
use HasSocialAccounts;
}
This gives you socialAccounts(), addSocialAccount(), findBySocialAccount(), and hasProvider().
3. Configure providers
In config/social-accounts.php, add entries to the providers array (google, facebook, github, …). Keys must match the Laravel Socialite driver name. Client credentials can come from services.{provider} in config/services.php as usual.
Where to look first, in order:
config/social-accounts.php — providers, route prefix/middleware, automatically_create_users, model/table/column overrides, gate names.src/Http/Controllers/ProviderController.php — the redirect/callback flow, including the auto-configuration of services.{provider}.redirect.src/Strategies/ — FindUser, FindOrCreateUser, AddSocialAccount; this is where login-vs-link behaviour is actually decided.First use case: link an account for a logged-in user
// settings page button — user is already authenticated
<a href="{{ route('social-accounts.login.github') }}">Connect GitHub</a>
The callback stores the account, fires SocialAccountAdded, and redirects back. Then:
$user->hasProvider('github'); // bool
$user->socialAccounts()->where('provider', 'github')->first(); // SocialAccount
Login (rather than linking) uses the same routes — the difference is automatically_create_users plus which strategy runs for an unauthenticated visitor.
Login vs. link is gate-driven, not route-driven. The callback checks two gates — login-with-provider and add-social-account — but only if you define them. If you need to block a suspended user from logging in via OAuth, define the gate; if you leave it undefined, there is no check.
Gate::define('login-with-provider', fn (User $user) => $user->is_active);
Per-provider Socialite tweaks via the facade. Scopes, with() parameters, and provider-specific method calls belong in a service provider's boot(), not in the config array:
use Audunru\SocialAccounts\Facades\SocialAccounts;
public function boot(): void
{
SocialAccounts::registerProviderSettings('google', 'with', [
'prompt' => 'select_account',
]);
}
This is the supported extension point for "I need one extra parameter on the driver" — you don't need to fork the controller.
Custom auth strategy. When the built-ins don't fit (e.g. match on provider_id only, invite-code required, merge into an existing email-matched user), implement Audunru\SocialAccounts\Interfaces\Strategy and point the config at your class. Keep the strategy thin: resolve or create the user, then let the package persist the SocialAccount row.
Events for post-link side effects. SocialAccountAdded (account linked) and SocialUserCreated (new user from OAuth) fire after persistence. Use them for onboarding emails, avatar import, or analytics — not for token refresh, which is a background concern.
Connected-accounts settings UI. The package registers an API resource route for index, show, and destroy on SocialAccount. That is enough for a "manage connected accounts" panel; check the route middleware in config before exposing it. Unlinking goes through destroy rather than hand-rolling a delete so any cleanup hooks stay consistent.
Token refresh is yours. Schedule a queued job that iterates accounts with expiring tokens, exchanges refresh_token at the provider, and updates the row. Keep provider-specific refresh logic in a service class and use backoff for rate limits — do not do this inline in a request.
Testing. The suite mocks Socialite's driver, provider, and ProviderUser contracts with Mockery, and the base TestCase calls SocialAccounts::emptyProviderSettings() before and after each test. Mirror that in your own tests — leftover registered provider settings across tests produce subtle, order-dependent failures. Run the package migrations manually in setUp() if you follow the package's own fixture pattern, and reset the facade state in tearDown().
automatically_create_users is false by default. The single most common "it doesn't work" report: OAuth login for a brand-new visitor fails until you flip this on. Linking from an authenticated session works regardless.config('social-accounts.providers') get social-accounts.login.{provider} and social-accounts.callback.{provider}. Adding a provider locally and not seeing the route almost always means config cache — php artisan config:clear.ProviderController sets services.{provider}.redirect from the named callback route if you haven't set it yourself. If you hardcode a redirect in config/services.php that differs by a trailing slash or scheme from what's registered at the provider, you get the classic redirect_uri_mismatch. Let the package generate it, or generate it with route() in your own config.user_id_column.type / unsigned before migrating. If your users table uses UUIDs, ULIDs, or a signed/non-bigint key, the published migration will fail or produce a mismatched foreign key. Fix the config first, then migrate — retrofitting means a manual migration.SocialAccount model class, table name, and column names are resolved from config at runtime. Override the config keys; subclass only to add behaviour (accessors, relationships, casts).token and refresh_token, and add encrypted casts on your extended model if your security posture requires it. Treat this as a decision you make explicitly, not one the package makes for you.google, facebook, github — not display names, not capitalised. The config key is passed straight through to Socialite.SocialAccounts::emptyProviderSettings() exists precisely because registerProviderSettings() accumulates. Forgetting it in a long test suite causes failures in tests that never touch OAuth.src/ProviderController.php and src/Strategies/ rather than trusting blog posts — several public write-ups of this package reference event and method names that don't match the current source. When in doubt, src/Events/ is ground truth.How can I help you explore Laravel packages today?