league/oauth2-client
OAuth 2.0 client library for PHP. Provides a solid base for implementing “Sign in with …” flows and RFC 6749-compliant integrations, with a GenericProvider for bearer-token services and an extensible architecture for custom providers.
Installation
composer require league/oauth2-client
For Laravel, use a service provider (e.g., League\OAuth2\Client\Provider\GithubProvider) or wrap it in a custom facade.
First Use Case: Authenticating with GitHub
use League\OAuth2\Client\Provider\GithubProvider;
$provider = new GithubProvider([
'clientId' => env('GITHUB_CLIENT_ID'),
'clientSecret' => env('GITHUB_CLIENT_SECRET'),
'redirectUri' => env('GITHUB_REDIRECT_URI'),
]);
// Generate auth URL
$authUrl = $provider->getAuthorizationUrl();
// Redirect user to $authUrl
// After redirect, handle callback
$token = $provider->getAccessToken('authorization_code', [
'code' => $request->query('code'),
]);
Where to Look First
League\OAuth2\Client\Provider\AbstractProvider (base class for custom providers).League\OAuth2\Client\Token\AccessToken (token handling).Provider Initialization
Extend AbstractProvider for custom providers or use built-in ones (e.g., Google, Facebook).
$provider = new CustomProvider([
'clientId' => config('oauth.client_id'),
'clientSecret' => config('oauth.client_secret'),
'redirectUri' => route('oauth.callback'),
'scopes' => ['user:read', 'repo'], // Optional
]);
Authorization Flow
$provider->getAuthorizationUrl().$token = $provider->getAccessToken('authorization_code', ['code' => $request->code]).Fetching User Data
$user = $provider->getResourceOwner($token);
// $user is a League\OAuth2\Client\Provider\ResourceOwnerInterface
Refreshing Tokens
$newToken = $provider->getAccessToken('refresh_token', ['refresh_token' => $token->getRefreshToken()]);
$this->app->bind('oauth.github', function () {
return new GithubProvider(config('services.github'));
});
public function handle($request, Closure $next) {
if (!$request->user()->token) {
return redirect()->route('oauth.login');
}
return $next($request);
}
HasApiTokens trait or a custom table:
$user->forceFill([
'access_token' => $token->getToken(),
'refresh_token' => $token->getRefreshToken(),
])->save();
['email', 'profile']).Session to store/validate state for CSRF protection.League\OAuth2\Client\Exception\RateLimitException gracefully.\Log::debug('OAuth Token', $token->jsonSerialize());
Redirect URI Mismatch
redirectUri in config matches the callback URL registered with the provider.url()->current() or route('oauth.callback') dynamically.Token Expiry
refresh_token if available or re-authenticate.expires timestamp and auto-refresh before expiry.Provider-Specific Quirks
access_type=offline for refresh tokens.code in query params; other providers may use POST.getAuthorizationUrl(), getAccessToken(), and getResourceOwner().CSRF/State Handling
state parameter and validate it on callback.$state = $request->session()->getState();
if ($state !== $request->query('state')) {
throw new \RuntimeException('State mismatch');
}
Error Handling
League\OAuth2\Client\Exception\IdentityProviderException for provider errors.try {
$token = $provider->getAccessToken('authorization_code', ['code' => $code]);
} catch (\League\OAuth2\Client\Exception\IdentityProviderException $e) {
\Log::error('OAuth Error: ' . $e->getMessage());
return redirect()->route('oauth.login')->with('error', 'Authentication failed');
}
$provider->setHttpClient(new \GuzzleHttp\Client([
'debug' => true,
]));
getLastResponse() on the provider to debug API calls:
$response = $provider->getLastResponse();
\Log::debug($response->getBody());
Custom Providers
Extend AbstractProvider and implement:
getAuthorizationUrl()getAccessToken()getResourceOwner()getBaseAuthorizationUrl() (e.g., https://api.example.com/oauth/authorize)Token Storage
HasApiTokens or a custom TokenRepository.$token = $provider->getAccessToken(...);
$user->tokens()->create([
'token' => $token->getToken(),
'refresh_token' => $token->getRefreshToken(),
'expires_at' => $token->getExpires(),
]);
Middleware for Token Validation
public function handle($request, Closure $next) {
$token = $request->user()->token;
if ($token && $token->expires_at < now()) {
$provider = app('oauth.github');
$newToken = $provider->getAccessToken('refresh_token', [
'refresh_token' => $token->refresh_token,
]);
$token->update([
'token' => $newToken->getToken(),
'expires_at' => $newToken->getExpires(),
]);
}
return $next($request);
}
Scopes Management Dynamically add/remove scopes:
$provider->setScopes(['user:read', 'repo']); // Before getAuthorizationUrl()
How can I help you explore Laravel packages today?