composer require league/oauth2-facebook
.env:
FACEBOOK_CLIENT_ID=your_app_id
FACEBOOK_CLIENT_SECRET=your_app_secret
FACEBOOK_REDIRECT_URI=http://your-app.test/callback
FACEBOOK_GRAPH_VERSION=v18.0 # Use latest stable version
app/Providers/FacebookServiceProvider.php):
use League\OAuth2\Client\Provider\Facebook;
use League\OAuth2\Client\Provider\FacebookUser;
class FacebookServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('facebook', function ($app) {
return new Facebook([
'clientId' => $app['config']['services.facebook.client_id'],
'clientSecret' => $app['config']['services.facebook.client_secret'],
'redirectUri' => $app['config']['services.facebook.redirect_uri'],
'graphApiVersion' => $app['config']['services.facebook.graph_version'],
]);
});
}
}
config/services.php:
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect_uri' => env('FACEBOOK_REDIRECT_URI'),
'graph_version' => env('FACEBOOK_GRAPH_VERSION', 'v18.0'),
],
Create a controller to handle the OAuth flow:
use League\OAuth2\Client\Provider\Facebook;
use League\OAuth2\Client\Provider\Exception\FacebookProviderException;
class AuthController extends Controller
{
protected $facebook;
public function __construct(Facebook $facebook)
{
$this->facebook = $facebook;
}
public function redirectToFacebook()
{
$authUrl = $this->facebook->getAuthorizationUrl([
'scope' => ['email', 'public_profile'],
]);
session(['oauth2state' => $this->facebook->getState()]);
return redirect()->to($authUrl);
}
public function handleCallback()
{
try {
$token = $this->facebook->getAccessToken('authorization_code', [
'code' => request('code'),
]);
$user = $this->facebook->getResourceOwner($token);
// Store user data in session/database
auth()->loginUsingId($user->getId(), true);
} catch (FacebookProviderException $e) {
return redirect('/login')->with('error', $e->getMessage());
}
return redirect('/dashboard');
}
}
// routes/web.php
Route::get('/login/facebook', [AuthController::class, 'redirectToFacebook']);
Route::get('/login/facebook/callback', [AuthController::class, 'handleCallback']);
// Sync user profile after login
public function syncProfile(FacebookUser $user)
{
User::updateOrCreate(
['email' => $user->getEmail()],
[
'name' => $user->getName(),
'avatar' => $user->getPictureUrl(),
'locale' => $user->getLocale(),
'provider_id' => $user->getId(),
]
);
}
public function checkTokenExpiry()
{
if (auth()->user()->token_expires_at < now()) {
return redirect()->route('facebook.login');
}
return response()->json(['status' => 'active']);
}
Store the OAuth state in the session to prevent CSRF:
// In AuthController
public function redirectToFacebook()
{
$authUrl = $this->facebook->getAuthorizationUrl(['scope' => ['email']]);
session(['oauth2state' => $this->facebook->getState()]);
return redirect()->to($authUrl);
}
public function handleCallback()
{
if (!session('oauth2state') || session('oauth2state') !== request('state')) {
throw new \Exception('Invalid state parameter.');
}
// Proceed with token exchange...
}
Fetch custom fields (e.g., birthday, work) dynamically:
public function getExtendedUserData($token, array $fields = [])
{
$response = $this->facebook->getHttpClient()->get(
'https://graph.facebook.com/me?' . http_build_query([
'fields' => implode(',', $fields),
'access_token' => $token->getToken(),
])
);
return json_decode($response->getBody(), true);
}
Graph API Version Lock-In:
v18.0) and test thoroughly when upgrading.// config/facebook.php
return [
'graph_version' => env('FB_GRAPH_VERSION', 'v18.0'),
];
Token Expiry Handling:
getLongLivedAccessToken() method:
$longLivedToken = $this->facebook->getLongLivedAccessToken($shortLivedToken->getToken());
Scope Deprecation:
user_about_me (renamed to user_birthday) or link (removed) may break your app. Check Facebook’s deprecated permissions regularly.toArray() method to debug missing fields:
dd($user->toArray());
App Secret Proof:
// Check if appsecret_proof is included in requests
$requestUrl = $this->facebook->getBaseAuthorizationUrl() . '?' . http_build_query([
'access_token' => $token->getToken(),
'appsecret_proof' => hash_hmac('sha256', $token->getToken(), config('services.facebook.client_secret')),
]);
Beta Tier Quirks:
enableBetaTier may expose unstable endpoints. Test thoroughly before production:
$provider = new Facebook([
'enableBetaTier' => true,
// ...
]);
Enable Guzzle Debugging:
Add this to your Facebook provider instance to log HTTP requests:
$provider = new Facebook([
'httpClient' => new \GuzzleHttp\Client([
'debug' => fopen('facebook_debug.log', 'w'),
]),
// ...
]);
Validate Token Responses:
Use getToken() and getExpires() to debug token issues:
dd($token->getToken(), $token->getExpires());
Handle Missing Scopes Gracefully:
Wrap getEmail() or getHometown() in try-catch:
try {
$email = $user->getEmail();
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
$email = null; // User didn’t grant email permission
}
Custom User Entity:
Extend FacebookUser to add app-specific methods:
class CustomFacebookUser extends FacebookUser
{
public function getFullProfile()
{
return $this->toArray() + [
'custom_field' => $this->getCustomField(),
];
}
protected function getCustomField()
{
return $this->getField('custom_field');
}
}
Override the provider’s createResourceOwner() method:
$provider = new Facebook([...]);
$provider->setResourceOwnerClass(CustomFacebookUser::class);
Custom HTTP Client: Replace the default HTTP client (e.g., for mocking in tests):
$provider = new Facebook([
'httpClient' => new \GuzzleHttp\Client([
'base_uri' => 'https://graph.facebook.com',
'headers' => ['User-Agent' => 'MyApp/1.0'],
How can I help you explore Laravel packages today?