Installation
composer require friends-of-symfony/oauth2-php
(Note: Redirect to FriendsOfSymfony/oauth2-php as per README.)
Basic Server Initialization
use FriendsOfSymfony\OAuth2\Server;
use FriendsOfSymfony\OAuth2\Storage\MemoryStorage;
$server = new Server();
$server->setStorage(new MemoryStorage()); // Replace with DB/Redis in production
$server->boot();
First Use Case: Token Endpoint
$request = Request::createFromGlobals();
$response = $server->handleTokenRequest($request);
$response->send();
Key Files to Review
config/oauth2.php (if creating custom config)app/Providers/OAuthServiceProvider.php (for binding storage)routes/api.php (for endpoint routing)Authorization Code Flow (Most Common)
// 1. Redirect user to auth endpoint
$authUrl = $server->getAuthorizationUrl(
$clientId,
$redirectUri,
['scope' => 'read write']
);
// 2. Handle callback
$request = Request::createFromGlobals();
$response = $server->handleAuthorizationRequest($request);
Resource Owner Password Credentials (Legacy)
$token = $server->getAccessToken(
$clientId,
$clientSecret,
$username,
$password,
$scope = null
);
Token Validation in API
$request = Request::createFromGlobals();
$token = $server->validateAuthenticatedRequest($request);
if (!$token) {
return response()->json(['error' => 'invalid_token'], 401);
}
Laravel Middleware for Token Validation
namespace App\Http\Middleware;
use Closure;
use FriendsOfSymfony\OAuth2\Server;
class ValidateOAuthToken
{
protected $server;
public function __construct(Server $server)
{
$this->server = $server;
}
public function handle($request, Closure $next)
{
if (!$this->server->validateAuthenticatedRequest($request)) {
return response()->json(['error' => 'invalid_token'], 401);
}
return $next($request);
}
}
Custom Storage Backend (e.g., Eloquent)
use FriendsOfSymfony\OAuth2\Storage\StorageInterface;
use App\Models\OAuthClient;
class EloquentStorage implements StorageInterface
{
public function getClientDetails($clientId)
{
return OAuthClient::where('id', $clientId)->first();
}
// Implement other required methods...
}
Scopes Handling
// Register scopes in storage
$storage->setScope('read', ['description' => 'Read access']);
$storage->setScope('write', ['description' => 'Write access']);
// Validate scopes in API
$token = $server->validateAuthenticatedRequest($request);
if (!$token->getScope()->hasScope('read')) {
abort(403);
}
Draft Version Mismatch
league/oauth2-client) for consistency.MemoryStorage for Production
MemoryStorage is not persistent across requests.StorageInterface with a database (e.g., Eloquent, Doctrine).CSRF Protection
state parameter in auth flow.Redirect URI Validation
redirect_uri against registered clients.$client = $storage->getClientDetails($clientId);
if ($client->getRedirectUri() !== $request->get('redirect_uri')) {
throw new \RuntimeException('Invalid redirect URI');
}
Enable Verbose Logging
$server->setLogger(new \Monolog\Logger('oauth', [
new \Monolog\Handler\StreamHandler(storage_path('logs/oauth.log'))
]));
Check Response Headers
400 Bad Request for invalid requests).Token Expiry Handling
Token::getExpiration() to check expiry and refresh tokens if needed.Custom Grant Types
$server->addGrantType(new \FriendsOfSymfony\OAuth2\Grant\CustomGrantType());
Token Enhancements
// Extend Token class
class ExtendedToken extends \FriendsOfSymfony\OAuth2\Token\Token
{
public function getCustomClaim()
{
return $this->claims['custom_claim'] ?? null;
}
}
Event Dispatching
authentication.success or token.issued:$server->on('authentication.success', function ($token) {
// Log or process token
});
State Parameter
state for authorization requests to prevent CSRF.$state = bin2hex(random_bytes(32));
session(['oauth_state' => $state]);
$authUrl = $server->getAuthorizationUrl($clientId, $redirectUri, ['state' => $state]);
Scope Validation
Client Secrets
client_secret is hashed in storage if using a database backend (e.g., bcrypt).How can I help you explore Laravel packages today?