league/oauth2-server-bundle
Symfony bundle integrating the League OAuth2 server library into your application. A maintained replacement for trikoder/oauth2-bundle, it provides OAuth2 authorization server endpoints with Doctrine persistence support and stays in sync with Symfony.
Verify your runtime — v2.0.0 bumped the required dependency floor:
^1.2 of the bundle, or upgrade PHP before adopting v2.Installation:
composer require league/oauth2-server-bundle:^2.0 doctrine/doctrine-bundle doctrine/orm
Enable the bundle in config/bundles.php:
return [
// ...
League\OAuth2ServerBundle\OAuth2ServerBundle::class => ['all' => true],
];
Migrating from 1.x — v2.0.0 removed the 1.x backward-compatibility layer. Expect:
ClientRepository, AccessTokenRepository, etc. to have changed.composer why-not league/oauth2-server-bundle 2.0 to surface blockers, then walk the config tree and remove deprecated keys before re-deploying.Database Migration:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
First Use Case:
php bin/console make:oauth2-client --name="MyAppClient" --redirect-uri="https://yourapp.com/callback"
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET" \
http://yourapp.test/oauth/token
Key Files:
config/packages/league_oauth2_server.yaml: Main configuration.src/Entity/: Auto-generated OAuth entities (clients, scopes, tokens).src/Security/: Custom authentication logic (if extending).Token Generation:
/oauth/authorize, /oauth/token) for authorization code, implicit, client credentials, password, and refresh flows.// src/Controller/OAuthController.php
use League\OAuth2\Server\ResourceServer;
class OAuthController extends AbstractController {
public function token(ResourceServer $server) {
$request = Request::createFromGlobals();
$server->validateAuthenticatedRequest($request);
$token = $server->issueAccessToken();
return new JsonResponse($token->jsonSerialize());
}
}
Resource Protection:
middleware mapping):
protected_route:
path: /api/protected
methods: GET
controller: App\Controller\ProtectedController::index
defaults:
_oauth: true
use League\OAuth2\Server\ResourceServer;
class ProtectedController {
public function index(ResourceServer $server) {
$request = Request::createFromGlobals();
$server->validateAuthenticatedRequest($request);
return new JsonResponse(['data' => 'protected']);
}
}
Custom Grant Types (v2 API — no legacy BC adapters):
GrantTypeInterface directly; the old 1.x compatibility shims are removed in 2.0.
use League\OAuth2\Server\Grant\GrantTypeInterface;
class CustomGrant implements GrantTypeInterface {
public function respondToAccessTokenRequest() { /* ... */ }
}
league_oauth2_server:
grants:
custom_grant: App\Security\CustomGrant
Scopes and Roles:
league_oauth2_server:
scopes:
read: Read access
write: Write access
User Authentication:
UserEntityInterface:
use League\OAuth2\Server\Entity\UserEntityInterface;
class AppUser implements UserEntityInterface {
public function getIdentifier() { /* ... */ }
}
Using it from a Laravel app:
laravel/passport for idiomatic Laravel support. If you must bridge, route through Symfony\Component\HttpFoundation and adapt the DI container manually; do not assume facades map to bundle services.Illuminate\Http\Request to a SymfonyRequest before hitting the ResourceServer.Testing:
ClientRepositoryInterface / ResourceOwnerRepositoryInterface and exercise the AuthorizationServer / ResourceServer directly. This sidesteps the bundle and is stable across the v1→v2 jump.
$clientRepo = $this->createMock(ClientRepositoryInterface::class);
$clientRepo->method('getClientEntity')->willReturn($clientEntity);
$server = new Server($clientRepo, $accessTokenRepo, $scopeRepo, $privateKey, $encryptionKey);
Caching:
league_oauth2_server:
cache:
enabled: true
pool: cache.app
Logging:
league_oauth2_server:
debug: true
v2.0.0 is a hard break from 1.x:
bin/console debug:container --tag=league_oauth2_server.* to confirm services are still registered after upgrade.Dependency floor jumps:
composer update will fail with Your requirements could not be resolved. Check with composer why-not before blaming the bundle.Database Schema Mismatches:
ClientEntity, AccessTokenEntity, etc.) can change between versions.php bin/console doctrine:migrations:diff --complete
CSRF Protection:
CsrfTokenListener where applicable.Token Expiry:
league_oauth2_server:
access_token_lifetime: 7200
refresh_token_lifetime: 2592000 # or null for non-expiring (not recommended)
Scope Validation:
$scopes = $server->getRequestedScopes();
if (!in_array('read', $scopes)) {
throw new \League\OAuth2\Server\Exception\OAuthServerException(
'insufficient_scope',
'The requested scope is invalid.',
\League\OAuth2\Server\ResponseTypes\AccessTokenResponse::class
);
}
Client Secret Handling:
league_oauth2_server:
private_key: '%env(OAUTH_PRIVATE_KEY)%'
encryption_key: '%env(OAUTH_ENCRYPTION_KEY)%'
CORS Issues:
nelmio_cors:
paths:
'^/oauth/':
allow_origins: ['%env(APP_ALLOWED_ORIGINS)%']
allow_methods: ['POST', 'OPTIONS']
Middleware Order:
ResourceServerMiddleware must run after the firewall middleware, otherwise the security context isn't populated when the resource server validates the token.Enable Debug Mode:
league_oauth2_server:
debug: true
var/log/dev.log for detailed traces.Token Validation Errors:
error / error_description in failed token responses:
invalid_client: client ID/secret mismatch.invalid_grant: bad authorization code or expired refresh token.unsupported_grant_type: grant not enabled for the client.Database Debugging:
doctrine:
dbal:
logging: true
profiling: true
Post-upgrade checklist (v1 → v2):
bin/console debug:container | grep oauth2.config/packages/league_oauth2_server.yaml against the v2 reference config — removed keys will silently be ignored or rejected depending on the tree builder.bin/console cache:clear --no-warmup and then warm up to surface constructor signature changes in repositories, grant types, and response types.How can I help you explore Laravel packages today?