ohauthcat / ohauthcat-php
OhAuthCat server SDK: enterprise SSO and SCIM directory sync
Requires
- php: >=8.2
- ext-json: *
Requires (Dev)
- guzzlehttp/guzzle: ^7.9
Suggests
- ext-curl: Used by the default transport when available; PHP streams are the fallback
- guzzlehttp/guzzle: A PSR-18 client for OhAuthCat\Psr18Transport (MIT)
- psr/http-client: Use any PSR-18 client through OhAuthCat\Psr18Transport
- psr/http-factory: Request and stream factories for OhAuthCat\Psr18Transport
Provides
None
Conflicts
None
Replaces
None
README
Server-side SDK for OhAuthCat enterprise SSO (SAML, OIDC) and SCIM directory sync. PHP 8.2+, no Composer runtime dependencies (uses ext-curl when available, PHP streams otherwise).
Install
composer require ohauthcat/ohauthcat-php:0.0.2
Documentation: https://docs.ohauthcat.com. Licensed under the OhAuthCat SDK Licence (see LICENSE); third-party notices are in THIRD_PARTY_LICENSES.
use OhAuthCat\OhAuthCatClient; use OhAuthCat\Webhooks; $client = new OhAuthCatClient(getenv('OHAUTHCAT_API_KEY'), 'pk_...'); $org = $client->organizations->getOrganization('org_...'); foreach ($client->directories->listDirectoryUsersAutoPaging('dir_...', ['limit' => 100]) as $user) { } // Webhooks: verify the raw body before parsing. $event = Webhooks::verify(file_get_contents('php://input'), $_SERVER['HTTP_OHAUTHCAT_SIGNATURE'] ?? null, $secret);
Sign-in
Every sign-in needs a random state that you keep in the user's session and compare on the callback, or an attacker can sign your user into the attacker's account (login CSRF). getAuthorizationUrl generates one when you pass none and returns it as $auth['state'], with the PKCE $auth['codeVerifier']. Refuse a missing or different state before exchanging the code:
<?php use OhAuthCat\RestartSignInException; const REDIRECT_URI = 'https://app.example.com/callback'; // Start: /login session_start(); $auth = $client->ssoHelpers->getAuthorizationUrl(REDIRECT_URI, domain: 'customer.com'); $_SESSION['sso_state'] = $auth['state']; $_SESSION['sso_verifier'] = $auth['codeVerifier']; header('Location: ' . $auth['url'], true, 302); exit; // Callback: /callback session_start(); $expected = (string) ($_SESSION['sso_state'] ?? ''); $verifier = (string) ($_SESSION['sso_verifier'] ?? ''); unset($_SESSION['sso_state'], $_SESSION['sso_verifier']); $state = $_GET['state'] ?? ''; if ($expected === '' || $verifier === '' || !is_string($state) || !hash_equals($expected, $state)) { http_response_code(400); // never exchange the code exit; } try { $result = $client->ssoHelpers->getProfileAndToken((string) ($_GET['code'] ?? ''), $verifier, REDIRECT_URI); // $result->getProfile(): create your session for this user } catch (RestartSignInException) { header('Location: /login', true, 302); // the code was used or expired; start again }
Guests: a sign-in admitted under the organization's guest policy (an email domain outside its verified domains) has AccessPolicy::isGuest($result->getProfile()) true (getAccessPolicy() is AccessPolicy::Guest->value); for every other sign-in the field is absent. email_domain_verified stays true for guests, so check this field when guests must get less access.
Portal links
A setup link lasts expires_in seconds, 300 (5 minutes) to 2,592,000 (30 days); leave it out for the default of 604,800 (7 days). A link created with a secret key opens once; revoke it to end it early, and expires_at on the result says when it ends. ttl (60 to 300 seconds) is only for sealed single-use embed links; never send both.
$link = $client->portal->createPortalLink(['organization' => 'org_...', 'intents' => ['sso'], 'expires_in' => 86400]); $link->getUrl();
- Every v1 operation is generated from the pinned spec. Responses are typed models
(
OhAuthCat\Models\*, for example$org->getName()), validated against the spec;toArray()is the raw JSON escape hatch. Request bodies take a model or an array. - Standby failover: after 3 consecutive edge failures the client moves to
api.oauthcat.com/auth.oauthcat.comand re-probes the primary every 60 s, for the client's lifetime (standby,standbyApiBaseUrl,standbyAuthBaseUrl,standbyThreshold,standbyCooldown). Request bodies are JSON objects: passnew \stdClass()for an empty object nested in a body. - Errors are
OhAuthCatExceptionsubclasses by type (NotFoundException,RateLimitedException, ...) with the stable$errorCode(seeGenerated\ErrorCodes). - To use a PSR-18 client such as Guzzle, install
psr/http-client,psr/http-factoryand the client, then passtransport: new OhAuthCat\Psr18Transport($client, $factory, $factory). Set timeouts on the client itself. Any other client can implementOhAuthCat\HttpTransport. - Writes carry an
Idempotency-Key(generated, ornew RequestOptions(idempotencyKey: ...)). GETs and keyed writes retry on 429/503 with backoff; the SSO code exchange is never retried.