jeffersongoncalves / laravel-sso-server
Central SSO identity provider for Laravel: Authorization Code + PKCE, RS256 JWT with JWKS and key rotation, one-time codes, and back-channel Single Logout webhooks.
Package info
github.com/jeffersongoncalves/laravel-sso-server
pkg:composer/jeffersongoncalves/laravel-sso-server
Requires
- php: ^8.2
- illuminate/auth: ^11.0|^12.0|^13.0
- illuminate/cache: ^11.0|^12.0|^13.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/queue: ^11.0|^12.0|^13.0
- illuminate/routing: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.24
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel SSO Server
Central SSO identity provider for Laravel: Authorization Code + PKCE, RS256 JWT with JWKS and key rotation, one-time codes, and back-channel Single Logout webhooks.
Installation
You can install the package via composer:
composer require jeffersongoncalves/laravel-sso-server
Publish and run the migrations, publish the config, then generate the first signing key:
php artisan vendor:publish --tag="sso-server-migrations" php artisan migrate php artisan vendor:publish --tag="sso-server-config" php artisan sso-server:keys
Usage
Register a client
php artisan sso-server:client "Billing" https://billing.test/sso/callback --slo=https://billing.test/sso/logout
Prints the client_id and client_secret. The secret is stored encrypted, since the server needs it to sign responses and webhooks.
Endpoints
The full protocol and client integration contract is in docs/architecture.md.
| Method | URI | Purpose |
|---|---|---|
| GET | /sso/authorize |
Authorization Code + PKCE (S256 only). Runs behind auth, so guests go to your login page first. Requires client_id, redirect_uri (exact match), state, code_challenge, code_challenge_method=S256. |
| POST | /sso/token |
Exchanges the one-time code (grant_type=authorization_code, client_id, client_secret, code, redirect_uri, code_verifier) for an RS256 access token. |
| GET | /sso/userinfo |
Current claims for a Bearer token. Signed with X-SSO-Timestamp + X-SSO-Signature. |
| GET | /.well-known/jwks.json |
Public keys used to verify access tokens. |
| GET | /sso/logout |
Client-initiated federated logout (browser redirect). Requires client_id and a token_hint issued to that client; optional same-origin post_logout_redirect_uri and state. |
Security model
- One-time codes: stored in cache (hashed) for
code_ttlseconds (default 60) and consumed with an atomicCache::add(), so a replayed code always fails. Use a cache store with atomicadd()(redis, memcached, database). - Tokens: RS256 JWTs with a
kidheader.sso-server:keysrotates the key and keepskeys.keeppairs (default 2), so tokens signed before the rotation still verify. - Signatures:
X-SSO-Signature = hash_hmac('sha256', "{X-SSO-Timestamp}.{raw body}", client_secret). Clients should reject stale timestamps. - Revocation: every issued token has an
sso_active_sessionsrow./sso/userinforejects tokens whose row is gone.
Single Logout
When the configured guard fires Laravel's Logout event, the server deletes the user's active sessions and queues a DispatchSingleLogoutJob for each client with a slo_webhook_url. The job POSTs a signed JSON body:
{"event": "logout", "sub": "42", "aud": "<client_id>", "iat": 1790000000, "jti": "<uuid>"}
You can also trigger it yourself: SsoServer::logoutUser((string) $user->id).
Custom claims
Implement SsoUserSerializerContract and set it in config/sso-server.php:
use Illuminate\Contracts\Auth\Authenticatable; use JeffersonGoncalves\SsoServer\Contracts\SsoUserSerializerContract; use JeffersonGoncalves\SsoServer\Models\SsoClient; class TenantUserSerializer implements SsoUserSerializerContract { public function serialize(Authenticatable $user, SsoClient $client): array { return [ 'name' => $user->name, 'email' => $user->email, 'email_verified' => $user->hasVerifiedEmail(), 'roles' => $user->getRoleNames(), 'tenant_id' => $user->tenant_id, ]; } }
Reserved claims (iss, sub, aud, iat, nbf, exp, jti) are always set by the server.
The default serializer sends name, email and email_verified. Keep email_verified in custom serializers: clients rely on it before linking accounts by email.
Maintenance
php artisan sso-server:prune # delete sessions expired more than 24h ago (--hours=N) php artisan sso-server:keys # rotate the signing key
On Windows, if key generation fails with an OpenSSL error, point SSO_SERVER_OPENSSL_CONF at an openssl.cnf.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.
