signward / idserver-client
PHP client SDK for Signward Identity Server — OIDC authentication for Laravel and other PHP apps.
Requires
- php: ^8.1
- ext-json: *
- ext-openssl: *
- firebase/php-jwt: ^7.0
- guzzlehttp/guzzle: ^7.5
Requires (Dev)
- illuminate/http: ^10.0 || ^11.0 || ^12.0
- illuminate/routing: ^10.0 || ^11.0 || ^12.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0
- phpunit/phpunit: ^10.5
README
PHP client SDK for Signward Identity Server — OIDC authentication for Laravel and any other PHP app. Open source (MIT), no Microsoft-stack dependency.
Features
- OIDC Authorization Code flow with PKCE
- Local JWT validation via the server's JWKS (HS256 + RS256), with discovery + JWKS caching
- Typed
Usermodel with built-in and per-tenant custom roles - Token exchange, refresh, userinfo, and end-session helpers
- First-class Laravel integration: auto-discovered service provider,
idserver.auth/idserver.rolemiddleware, bundled login routes, and anIdServerfacade - MIT licensed
Install
composer require signward/idserver-client
PHP 8.1+. The Laravel integration targets Laravel 10, 11, and 12.
Quickstart — Laravel
The package is auto-discovered. Publish the config and set your tenant credentials:
php artisan vendor:publish --tag=idserver-config
IDSERVER_AUTHORITY=https://mytenant.signward.com IDSERVER_CLIENT_ID=my-webapp IDSERVER_CLIENT_SECRET=...
Register https://myapp.com/auth/idserver/callback as a redirect URI on your
Signward client. The package mounts these routes automatically:
| Route | Purpose |
|---|---|
GET /auth/idserver/login |
Start the OIDC flow (redirects to Signward, with PKCE) |
GET /auth/idserver/callback |
Exchange the code for tokens, store them in the session |
GET|POST /auth/idserver/logout |
Clear the session and end the Signward session |
Protect routes with the bundled middleware:
use Illuminate\Support\Facades\Route; use Signward\IdServer\Laravel\Facades\IdServer; // Require a signed-in Signward user Route::middleware('idserver.auth')->group(function () { Route::get('/dashboard', function () { $user = IdServer::user(); // Signward\IdServer\Models\User return "Hello {$user->email}"; }); }); // Require a role (any-of). Built-in and per-tenant custom roles both count. Route::middleware('idserver.role:admin,editor')->get('/admin', fn () => 'Admin area');
IdServer::user() returns null when signed out; IdServer::check() is a quick
boolean. IdServer::client() exposes the underlying IdServerClient for advanced
use (refresh, userinfo, logout URL).
Quickstart — plain PHP (no framework)
use Signward\IdServer\IdServerClient; $client = IdServerClient::create( authority: 'https://mytenant.signward.com', clientId: 'my-webapp', clientSecret: '...', ); // 1) Redirect the user to the login page: ['url' => $url, 'verifier' => $verifier] = $client->authorizeUrlWithPkce( redirectUri: 'https://myapp.com/callback', state: 'random-state', ); // Store $verifier + state in the session, then redirect to $url. // 2) On callback (?code=...&state=...): $tokens = $client->exchangeCode($code, 'https://myapp.com/callback', $verifier); // 3) Validate the access token locally (signature, iss, aud, exp via JWKS): $user = $client->validateToken($tokens->accessToken); echo $user->email, ' ', implode(',', $user->roles); // 4) Or fetch userinfo remotely: $user = $client->userinfo($tokens->accessToken); // 5) Later, refresh: $fresh = $client->refreshToken($tokens->refreshToken); // 6) Logout URL: $logout = $client->endSessionUrl( idTokenHint: $tokens->idToken, postLogoutRedirectUri: 'https://myapp.com/', );
Validating tokens in an API
To protect a stateless API (no login flow), validate the incoming bearer token:
use Signward\IdServer\IdServerClient; use Signward\IdServer\IdServerOptions; use Signward\IdServer\Exceptions\InvalidTokenException; $client = new IdServerClient(new IdServerOptions( authority: 'https://mytenant.signward.com', clientId: 'my-api', audience: 'idserver-api', // expected `aud` claim )); try { $user = $client->validateToken($bearerToken); } catch (InvalidTokenException $e) { http_response_code(401); exit; } if (!$user->hasAnyRole(['admin'])) { http_response_code(403); exit; }
User model
final class User { public readonly ?string $userId; // "sub" claim public readonly ?string $email; public readonly ?bool $emailVerified; public readonly ?string $name; public readonly ?string $tenantId; public readonly array $roles; // built-in roles public readonly array $customRoles; // per-tenant RBAC roles public readonly array $claims; // raw JWT / userinfo payload public function hasRole(string $role): bool; public function hasCustomRole(string $role): bool; public function hasAnyRole(array $roles, bool $includeCustom = true): bool; public function hasAllRoles(array $roles, bool $includeCustom = true): bool; }
Configuration
config/idserver.php (publishable). All values default from environment variables:
return [ 'authority' => env('IDSERVER_AUTHORITY'), 'client_id' => env('IDSERVER_CLIENT_ID'), 'client_secret' => env('IDSERVER_CLIENT_SECRET'), 'scopes' => ['openid', 'profile', 'email', 'roles'], 'audience' => env('IDSERVER_AUDIENCE'), 'verify_ssl' => env('IDSERVER_VERIFY_SSL', true), 'routes' => ['enabled' => true, 'prefix' => 'auth/idserver', 'middleware' => ['web']], 'redirects' => ['after_login' => '/', 'after_logout' => '/'], ];
Outside Laravel, pass an IdServerOptions to the client directly (see above).
Error handling
All exceptions extend Signward\IdServer\Exceptions\IdServerException:
use Signward\IdServer\Exceptions\InvalidTokenException; use Signward\IdServer\Exceptions\TokenExchangeException; use Signward\IdServer\Exceptions\IdServerException; try { $user = $client->validateToken($token); } catch (InvalidTokenException $e) { // bad signature / issuer / audience / expiry } catch (TokenExchangeException $e) { echo $e->error, ' ', $e->description, ' ', $e->statusCode; } catch (IdServerException $e) { // discovery / userinfo / transport error }
License
MIT