novosga/oauth-server-bundle
OAuth 2.0 server bundle for the NovoSGA ecosystem, providing authorization and token endpoints so applications can issue access tokens and secure API access. Integrates into PHP projects to handle client authentication, grants, and protected resources.
This is a Symfony 5 bundle, not a Laravel package. If you are working in Laravel, the day-to-day answer is: you will not use this package — you'd reach for Laravel Passport/Sanctum, or consume a Novo SGA instance's /oauth/v2/token endpoint with Http::asForm()->post(...). Everything below assumes you are inside a Symfony 5 app (or evaluating the package to expose OAuth2 from a Novo SGA-adjacent service).
Minimal steps:
Require it and check the platform constraints first — the 2022-03 release targets Symfony 5:
composer require novosga/oauth-server-bundle
It is a maintained-fork of the abandoned friendsofsymfony/oauth-server-bundle (which wraps bshaffer/oauth2-server-php). Do not expect Symfony 6/7 support.
Register the bundle in config/bundles.php:
FOS\OAuthServerBundle\FOSOAuthServerBundle::class => ['all' => true],
Import the routes in config/routes.yaml:
fos_oauth_server_token:
resource: "@FOSOAuthServerBundle/Resources/config/routing/token.xml"
fos_oauth_server_authorize:
resource: "@FOSOAuthServerBundle/Resources/config/routing/authorize.xml"
Configure config/packages/fos_oauth_server.yaml:
fos_oauth_server:
db_driver: orm
client_class: App\Entity\OAuthClient
access_token_class: App\Entity\OAuthAccessToken
refresh_token_class: App\Entity\OAuthRefreshToken
auth_code_class: App\Entity\OAuthAuthCode
service:
user_provider: App\Security\UserProvider
options:
access_token_lifetime: 3600
refresh_token_lifetime: 1209600
Create entity subclasses extending the bundle's mapped superclasses (FOS\OAuthServerBundle\Entity\Client, AccessToken, RefreshToken, AuthCode) with an id property each, then generate schema:
bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate
Provision a client and hit the token endpoint:
bin/console fos:oauth-server:create-client --redirect-uri=https://app.example/cb --grant-type=authorization_code
curl -X POST http://localhost:8000/oauth/v2/token \
-d grant_type=client_credentials \
-d client_id=$CLIENT_ID -d client_secret=$CLIENT_SECRET
First use case: machine-to-machine client_credentials tokens for an internal API, or authorization_code for a third-party integration that needs to act on behalf of a Novo SGA user.
Pick the grant type per consumer, not per app.
client_credentials — service-to-service, no user context. Cheapest to operate.password — trusted first-party clients only (mobile/SPA you control). Requires wiring the user_provider so the bundle can load users by username.authorization_code — third-party apps. This is the only flow that needs the /oauth/v2/auth routes, a login form, and the authorize template.refresh_token — always pair with authorization_code/password; issue short access tokens (15–60 min) and long refresh tokens.Firewall layout that actually works:
security:
firewalls:
oauth_token:
pattern: ^/oauth/v2/token
security: false # must be anonymous
oauth_authorize:
pattern: ^/oauth/v2/auth
form_login:
provider: app_user_provider
check_path: /oauth/v2/auth_login_check
login_path: /oauth/v2/auth_login
anonymous: lazy
api:
pattern: ^/api
stateless: true
fos_oauth: true
The fos_oauth: true firewall validates the bearer token and populates the token in the security context; use it rather than hand-rolling a listener.
Override the authorize screen rather than forking the bundle:
templates/bundles/FOSOAuthServerBundle/Authorize/authorize.html.twig
The controller renders this path by convention; replacing the file is the supported extension point.
Programmatic client management (useful for provisioning tenants):
$manager = $container->get('fos_oauth_server.client_manager');
$client = $manager->createClient();
$client->setRedirectUris(['https://tenant.example/cb']);
$client->setAllowedGrantTypes(['authorization_code', 'refresh_token']);
$manager->updateClient($client);
// persist $client->getPublicId() and $client->getSecret() into env/secret store
Protect routes with scopes — the server exposes $server->verifyResourceRequest(); the simplest reliable pattern is to keep fos_oauth: true for authentication and add a small kernel.controller listener or voter for scope checks based on $token->getScope().
Token hygiene — write a scheduled command that deletes rows from the access/refresh token tables where expiresAt < now(). The bundle never cleans up; token tables become the largest tables in the schema within months.
Testing — use WebTestCase + KernelBrowser against /oauth/v2/token, and in unit tests replace fos_oauth_server.server with a mock. For integration suites, seed a dedicated test client via fixture and assert the JSON error shape (error, error_description) rather than status codes alone.
composer.json and put a migration ticket in the backlog the day you install it. The intended successor path is league/oauth2-server with oauth2-server-bundle or a thin PSR-7 bridge.composer why-not symfony/framework-bundle 6.4 before you spend time debugging config that "should work" — it won't on Symfony 6+.*_class config = confusing Doctrine errors. If you omit refresh_token_class or auth_code_class, the schema tool produces partial tables and the token endpoint throws during hydration. Configure all four classes even if you only use client_credentials.auto_mapping. The bundle ships XML mappings for its abstract classes; if you disable auto-mapping or scope entity managers, you must register the bundle's mapping explicitly or the inherited fields ($expiresAt, $token, $scope) never appear in the schema./oauth/v2/token must be anonymous. A form_login-guarded firewall on the whole /oauth prefix is the single most common misconfiguration — the token endpoint returns a login redirect instead of JSON, which clients report as "invalid JSON response."bshaffer/oauth2-server-php reads Authorization: Bearer and the access_token query/body param. Nginx/Apache under CGI, and some reverse proxies, drop the Authorization header — the symptom is 401 on every call while curl locally succeeds. Confirm with getallheaders() or an echo endpoint behind the same proxy.fos_oauth_server.controller.token service if you need extra claims (e.g., expires_in tuning, tenant id). Avoid editing vendor code; the bundle's service definitions are stable enough to override in services.yaml.bin/console debug:container fos_oauth_server.server to inspect grant types; bin/console debug:router | grep oauth to confirm routes; and watch var/log/dev.log for OAuth2\ServerException — the bundle logs the underlying library's exception with the real cause (bad client, expired code, mismatched redirect URI) while the HTTP response only says invalid_grant.Http::asForm() and store access_token/refresh_token/expires_in; refresh with the refresh_token grant. Do not attempt to install this bundle in a Laravel app.How can I help you explore Laravel packages today?