PHP quickstart
PHP 8.1+, with Laravel and Symfony integrations.
composer require latchvector/sso
This SDK requires firebase/php-jwt ^7.1. Everything below 7.0.0 is affected by
CVE-2025-45769; do not work around Composer refusing older versions.
The SDK wraps every endpoint: authentication, and the full management surface (users, organizations, roles, applications, API clients, webhooks, audit, bulk import, data export/erase, MFA & devices). Reach for the API reference only to look up an exact request/response shape.
1. Protect an API
Native integrations for Laravel and Symfony guard routes with framework
middleware; a framework-agnostic TokenVerifier covers everything else.
Laravel
// routes/web.php — the service provider registers the middleware for you
Route::get('/invoices', [InvoiceController::class, 'index'])
->middleware('sso.auth');
Route::post('/invoices/{id}/approve', [InvoiceController::class, 'approve'])
->middleware(['sso.auth', 'sso.can:invoice.approve']);
Symfony
# security.yaml — wire the authenticator onto your firewall
security:
firewalls:
api:
custom_authenticators:
- LatchVector\Sso\Symfony\SsoAuthenticator
Any framework
use LatchVector\Sso\TokenVerifier;
$verifier = new TokenVerifier(
issuer: 'https://sso.yourdomain.com',
audience: 'https://api.yourcompany.com',
cache: $psr16Cache, // do not skip — see below
);
$principal = $verifier->verifyAuthorizationHeader($request->getHeaderLine('Authorization'));
PHP dies at the end of every request, so a PSR-16 cache for the signing keys
is required, not optional (the Laravel provider wires this up; on Symfony pass
@cache.app).
2. Log a user in
use LatchVector\Sso\SsoClient;
use LatchVector\Sso\TokenPair;
$sso = new SsoClient('https://sso.yourdomain.com', 'https://api.yourcompany.com');
$result = $sso->login($email, $password);
if ($result instanceof TokenPair) {
$access = $result->accessToken;
$refresh = $result->refreshToken;
}
3. Manage everything — one client
ManagementClient wraps the entire management surface, one method per
endpoint, so the whole product is reachable from code — you rarely write a raw
request.
use LatchVector\Sso\ManagementClient;
$mgmt = new ManagementClient($issuer, fn () => $currentAccessToken);
$mgmt->createUser(['organizationId' => $orgId, 'email' => $email, 'fullName' => $name]);
$mgmt->createOrganization(['name' => $name, 'slug' => $slug, 'parentId' => $parentId]);
$mgmt->createRole(['organizationId' => $orgId, 'name' => 'Doctor',
'scope' => 'SUBTREE', 'permissionCodes' => ['USER_MANAGE']]);
$mgmt->createApplication(['organizationId' => $orgId, 'identifier' => $id, 'name' => $name]);
$mgmt->registerClient(['organizationId' => $orgId, 'name' => $name, 'scopes' => ['reports.read']]);
$mgmt->registerWebhook(['organizationId' => $orgId, 'applicationId' => $appId, 'url' => $url]);
$mgmt->searchAudit(['organizationId' => $orgId, 'q' => 'role.revoked']);
$mgmt->importValidate($payload); // bulk migrate
$mgmt->eraseUser($userId); // export / erase
$mgmt->mfaSetupBegin(); // the caller's own MFA
$mgmt->listMyDevices(); // the caller's devices
$mgmt->provisionSandbox([/* … */]); // platform operators
Anything not covered by a method — including endpoints added after this SDK
shipped — is reachable via $mgmt->request($method, $path, query: [...], body: [...]).
The Packagist README has the full recipes (multitenancy with Laravel, go-live checks). Exact request/response shapes: API reference.