friendsofsymfony/oauth2-php
PHP OAuth2 library by FriendsOfSymfony providing client/server building blocks: token and authorization flows, grant types, access token handling, and extensible components for integrating OAuth2 authentication into Symfony and other PHP apps.
Installation
composer require friendsofsymfony/oauth2-php
Add the service provider to config/app.php:
'providers' => [
// ...
FriendsOfSymfony\OAuth2ServerBundle\FriendsOfSymfonyOAuth2ServerBundle::class,
],
Configuration
Copy the default config to config/packages/friendsofsymfony_oauth2_server.yaml:
php bin/console config:dump-reference friendsofsymfony_oauth2_server > config/packages/friendsofsymfony_oauth2_server.yaml
Define your grant types, clients, and scopes in the config.
First Use Case: Token Endpoint
Test the /oauth/v2/token endpoint with a POST request:
curl -X POST \
-d "grant_type=password&client_id=your_client_id&client_secret=your_secret&username=user&password=pass" \
http://your-app.test/oauth/v2/token
Verify the response includes an access_token.
config/packages/friendsofsymfony_oauth2_server.yaml (grants, clients, scopes).config/routes/oauth2.yaml (default /oauth/v2/*).src/Entity/ (customize Client, AccessToken, RefreshToken, etc.).src/Service/ (extend AuthorizationServer, ResourceOwnerPasswordTokenProvider, etc.).// In a controller or service
$token = $this->oauth2->getToken('password', [
'username' => 'user@example.com',
'password' => 'securepassword',
'client_id' => 'client_id',
'client_secret' => 'client_secret',
]);
Use Case: Direct user authentication (e.g., mobile apps, trusted clients).
$token = $this->oauth2->getToken('client_credentials', [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'scope' => 'read write',
]);
Use Case: Background services or APIs calling other APIs.
/oauth/v2/auth?response_type=code&....$token = $this->oauth2->getToken('authorization_code', [
'code' => $request->input('code'),
'redirect_uri' => 'https://your-app.com/callback',
'client_id' => 'client_id',
'client_secret' => 'client_secret',
]);
// src/Service/CustomTokenProvider.php
namespace App\Service;
use FriendsOfSymfony\OAuth2ServerBundle\Provider\TokenProviderInterface;
use FriendsOfSymfony\OAuth2ServerBundle\Model\ClientInterface;
use FriendsOfSymfony\OAuth2ServerBundle\Model\AccessTokenInterface;
class CustomTokenProvider implements TokenProviderInterface
{
public function getToken(ClientInterface $client, array $params)
{
// Custom logic (e.g., validate API keys, add metadata to tokens)
return new AccessToken($params['scope'], $client, 3600);
}
}
Register in config/packages/friendsofsymfony_oauth2_server.yaml:
oauth2:
token_providers:
custom: App\Service\CustomTokenProvider
# config/packages/friendsofsymfony_oauth2_server.yaml
oauth2:
scopes:
custom_scope: "Access custom feature"
Use in requests:
curl -d "scope=custom_scope" ...
// src/Http/Middleware/OAuth2Middleware.php
namespace App\Http\Middleware;
use Closure;
use FriendsOfSymfony\OAuth2ServerBundle\Model\AccessTokenInterface;
use FriendsOfSymfony\OAuth2ServerBundle\OAuth2\OAuth2;
class OAuth2Middleware
{
public function __construct(private OAuth2 $oauth2) {}
public function handle($request, Closure $next)
{
$token = $this->oauth2->validateBearerToken($request->bearerToken());
if (!$token) {
abort(401, 'Invalid token');
}
// Attach token to request or user
$request->merge(['oauth_token' => $token]);
return $next($request);
}
}
Register in app/Http/Kernel.php:
protected $routeMiddleware = [
'auth.oauth' => \App\Http\Middleware\OAuth2Middleware::class,
];
Protect routes:
Route::middleware('auth.oauth')->get('/api/protected', ...);
// src/Entity/Client.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use FriendsOfSymfony\OAuth2ServerBundle\Entity\Client as BaseClient;
#[ORM\Entity]
class Client extends BaseClient
{
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $apiKey;
// Getters/setters...
}
Update config/packages/doctrine.yaml to point to your entities.
CSRF Protection
authorization_code grant requires state and redirect_uri validation.state parameter or implement CSRF tokens manually.Token Expiry
oauth2:
access_token_ttl: 3600 # 1 hour in seconds
refresh_token_ttl: 2592000 # 30 days
Scope Validation
Client Secrets in URLs
client_secret in URLs (e.g., redirect_uri). Use the PKCE flow for public clients:
oauth2:
grants:
authorization_code:
pkce: true
Enable Verbose Logging
# config/packages/monolog.yaml
handlers:
oauth2:
type: stream
path: "%kernel.logs_dir%/oauth2.log"
level: debug
channels: ["oauth2"]
Add to config/packages/friendsofsymfony_oauth2_server.yaml:
oauth2:
debug: true
Test with Postman
Bearer tokens.authorization_code, simulate the redirect flow manually.Database Seeds Create a seeder to populate clients/scopes:
// database/seeds/OAuth2Seeder.php
use App\Entity\Client;
use FriendsOfSymfony\OAuth2ServerBundle\Entity\Scope;
public function run()
{
$client = new Client();
$client->setRandomId();
$client->setSecret('secret');
$client->setRedirectUris(['https://example.com/callback']);
$this->getEntityManager()->persist($client);
$scope = new Scope();
$scope->setKey('read');
$scope->setDescription('Read access');
$this->getEntityManager()->persist($scope);
$this->getEntityManager()->flush();
}
Custom Grant Types
Implement FriendsOfSymfony\OAuth2ServerBundle\Provider\GrantTypeProviderInterface:
class CustomGrantProvider implements GrantTypeProviderInterface
{
public function getGrantType()
{
return 'custom_grant';
}
public function validate(array $params)
{
// Custom validation logic
return ['access_token' => '...'];
}
}
Register in config:
oauth2:
grants:
custom_grant: App\Service\CustomGrantProvider
Token Storage
Extend FriendsOfSymfony\OAuth2ServerBundle\Entity\AccessToken to store additional metadata (e.g., IP, user agent):
#[ORM\Column(type: 'string', length: 45, nullable: true)]
private ?string $userAgent;
Rate Limiting Use middleware to limit token requests:
// src/Http/Middleware/RateLimitOAuth.php
use Symfony\Component\Rate
How can I help you explore Laravel packages today?