cartalyst/sentinel-social
Adds social OAuth login to Cartalyst Sentinel, letting users sign in with Facebook, Google, Twitter and other providers while their accounts persist through Sentinel's user repository. Extends Sentinel's authentication flow with social provider support for Laravel applications.
Prerequisite: this package is only worth installing when Cartalyst Sentinel is already the source of truth for users in your app. It does not extend Auth::guard(); it resolves an OAuth identity and hands it back to Sentinel's own repositories, hashing, session persistence, and throttling. If you are on Breeze/Jetstream/Fortify or plain Auth, stop here — you would be adding a second auth stack for no gain.
Minimal path to a working Google login:
Sentinel::authenticate([...]), Sentinel::login($user), and Sentinel::check() should already be running in your app before you touch this package. Everything in Sentinel Social assumes those are healthy.NOASSERTION — get legal clearance before you spend engineering time. Pin an exact version and commit composer.lock:
composer require cartalyst/sentinel-social:6.1.1
composer.json (PHP/Laravel/Sentinel constraints — this is your compatibility truth, not the release note), src/ (the service provider plus the SocialManager-style class you'll call), resources/config/ (the provider registry you edit), and src/Provider/ (the template a custom provider must match).php artisan vendor:publish --provider="..." finds nothing. Then fill in client_id, client_secret, and the exact callback URL per provider.// routes/web.php
Route::get('auth/{provider}', [SocialController::class, 'redirect']);
Route::get('auth/{provider}/callback', [SocialController::class, 'callback']);
// SocialController
public function redirect(string $provider)
{
session(['social.provider' => $provider]);
return SentinelSocial::with($provider)->redirect();
}
public function callback()
{
$provider = session()->pull('social.provider');
try {
$user = SentinelSocial::with($provider)->authenticate();
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
report($e);
return redirect()->route('login')->withErrors('Social sign-in failed.');
}
if (! $user) {
return redirect()->route('register.social'); // link or register flow
}
Sentinel::login($user, true);
return redirect()->intended('/dashboard');
}
Model the three intents explicitly, even if you only ship one. The manager exposes intent methods rather than a single login() — expect authenticate() (resolve provider user → find linked Sentinel user → return it or null), a register()-style path (create a Sentinel user from provider data), and a separate linking path for an already-authenticated Sentinel user. Build all three routes in the first release. Retrofitting "attach my Google account" after users have already registered with passwords means reconciling email collisions in production, which is far more expensive than wiring the route up front.
Treat the manager as a credential source, not a user-provisioning system. Sentinel Social should answer "who is this OAuth user?" — your app still owns activation, roles, and onboarding. After a register() succeeds, run your normal pipeline:
$user->roles()->attach(Sentinel::findRoleBySlug('member')->id);
// dispatch welcome mail, fire your NewUserRegistered event, etc.
Do not let the package's defaults decide whether a social user is active. On most Sentinel setups a freshly created user is inactive until activated — verify this explicitly, or nobody can log in and you'll spend an afternoon on it.
Custom providers are the actual headline feature. If you're integrating a regional or enterprise IdP, you write a class, not a fork:
// app/Social/Provider/AcmeProvider.php
class AcmeProvider extends \League\OAuth2\Client\Provider\AbstractProvider
{
public function getBaseAuthorizationUrl() { /* ... */ }
public function getBaseAccessTokenUrl(array $params) { /* ... */ }
public function getResourceOwnerDetailsUrl(AccessToken $token) { /* ... */ }
protected function createResourceOwner(array $response, AccessToken $token)
{
return new AcmeResourceOwner($response); // normalize: uid, email, name
}
}
Register it under providers in the published config with a short key, then call it by key: SentinelSocial::with('acme')->authenticate(). Keep the provider class free of Sentinel logic — token exchange and user normalization only. That separation is what makes the class testable and the manager swappable.
Route layout that scales. A /{provider} prefix with an allow-list beats one route pair per provider. Resolve the provider from the configured registry and 404 on anything not registered — never pass an arbitrary string to with().
Store provider intent in the session, not the callback query string. A ?provider=google on the callback is user-controlled and pairs badly with state validation. Set it on redirect, pull it on callback.
Abstract the manager behind your own interface. Live OAuth round-trips in CI are slow and flaky. Define SocialAuthenticator in your app, bind it to a thin wrapper around the package manager, and swap in a fake returning a canned Sentinel user in tests. This also gives you a single seam to replace the package later without touching controllers.
Tokens are not your problem — until they are. The package resolves identity; it does not persist access/refresh tokens for you to call the provider's API later. If you anticipate "post to Facebook on the user's behalf", capture the token in the callback yourself and store it encrypted. Deciding this after launch means re-prompting every user for consent.
http vs https, www vs bare, trailing slash, port, and subdomain all count. Behind a load balancer, TrustProxies and APP_URL must be right or Laravel generates a redirect URI the provider rejects as redirect_uri_mismatch. Log the exact generated URI once and diff it against the console.api vs web), state checks fail and the error looks unrelated to sessions. Keep both routes in web.Auth::login() with Sentinel::login(). Sentinel has its own session keys and Sentinel::check(). Code that assumes Auth::user() will return null after a social login and you'll chase it for hours.null into a unique email column — branch to an explicit "confirm your email" step. Same for email collisions: a Google login with an email that already exists as a password account should route to the link flow, not silently create a duplicate or auto-merge.env(). If you publish the config, php artisan config:cache after every change, and note that any env() inside it is baked at cache time. Read secrets in config, never in provider classes.invalid_grant, redirect_uri_mismatch, expired codes, and denied consent all surface identically as IdentityProviderException to the user. Log $e->getMessage() and the response body; add a request ID so support can correlate a user report with a log line.league/oauth2-facebook fork → upstream), there are zero dependents, and the license is NOASSERTION. Verify the Composer constraints against your PHP/Laravel before adopting, and assume you may need to fork when a League OAuth2 major moves. Keeping the manager behind your own interface is what makes that a weekend rather than a quarter.unlink(), custom user resolution, a different persistence path — extend the manager and rebind the container binding in your own service provider. Editing vendor/ guarantees you lose the change on the next composer update.authenticate(). A social callback that resolves a user and calls Sentinel::login() directly may bypass it entirely — verify and apply throttling yourself if so.How can I help you explore Laravel packages today?