PHP / Laravel SDK
Integrate Signward authentication into Laravel and other PHP apps with the signward/idserver-client Composer package.
PHP / Laravel SDK
The signward/idserver-client package adds Signward OIDC authentication to
Laravel and any other PHP app. Open source (MIT), no Microsoft-stack
dependency.
- 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
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, and IdServer::client() exposes the underlying IdServerClient for
advanced use (refresh, userinfo, logout URL).
Quickstart — protecting 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;
}
The signature is verified against the server's JWKS (discovered automatically and
cached); iss, aud, and exp are enforced.
Quickstart — plain PHP login flow
For a non-Laravel app that performs the OIDC flow itself:
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:
$user = $client->validateToken($tokens->accessToken);
// 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/',
);
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
}
Other SDKs
- .NET SDK — ASP.NET Core, NuGet
- Python SDK — FastAPI and Flask integration
- JavaScript / TypeScript SDK — Node.js, Express, and the browser