ory/kratos-client-php
The official PHP client for Ory Kratos, the open-source identity and user management server. Generated from Ory's OpenAPI spec, it gives Laravel and PHP apps typed access to Kratos APIs for registration, login, sessions, and account recovery.
This is a thin, OpenAPI-generated HTTP client — not a Laravel package. There is no service provider, no facade, and no config file to publish. Expect to write a small wrapper around it.
composer require ory/kratos-client-php
The generated code lives under Ory\Kratos\Client\:
Configuration — host, auth, HTTP clientApi\FrontendApi — session checks, browser-flow endpoints (toSession, getSession, listMySessions)Api\IdentityApi — admin CRUD on identities, sessions, recovery linksApi\MetadataApi — isReady, isAlive, getVersion (use this for health checks)Start by reading the OpenAPI spec on Ory's docs (search for kratos.openapi.yaml) rather than the PHP source — the methods are 1:1 with the spec and the generated PHPDoc is your only real documentation.
First use case — "is this request logged in?"
use Ory\Kratos\Client\Api\FrontendApi;
use Ory\Kratos\Client\Configuration;
use Ory\Kratos\Client\ApiException;
$config = Configuration::getDefaultConfiguration();
$config->setHost(config('services.kratos.public_url')); // e.g. http://kratos:4433
$frontend = new FrontendApi(null, $config);
try {
$session = $frontend->toSession(null, $request->cookie('ory_kratos_session'));
} catch (ApiException $e) {
// 401 => no/invalid session
}
Admin operations need a bearer token:
$config->setAccessToken(config('services.kratos.admin_token')); // ORY_SESSION_TOKEN
$identityApi = new IdentityApi(null, $config);
$identity = $identityApi->getIdentity($id);
Wrap it in one service. Bind a single KratosClient in a service provider that exposes frontend(), identity(), metadata() and centralises host/token/timeouts. Every controller and middleware then injects that wrapper — you never instantiate Configuration at call sites.
Middleware auth, not a Laravel guard first. The pragmatic path is an AuthenticateKratos middleware: read the ory_kratos_session cookie, call toSession, and on success attach a hydrated KratosUser (implementing Authenticatable) to the request. Registering a full custom guard/provider is possible but the identity payload (traits come back as a plain object, not typed columns) usually fits your users table poorly — hydrate a lightweight value object instead and keep Laravel's own guard for authorization checks.
Pass the session explicitly. Behind nginx/Octane the browser cookie is not automatically forwarded; always extract ory_kratos_session and pass it as the $cookie argument (or use getSession with an X-Session-Token). Do not rely on the SDK to carry cookies.
Admin workflows map cleanly: listIdentities(pageSize, pageToken, ...) for user admin screens (paginate with pageToken, there is no offset), createIdentity(new CreateIdentityBody([...])) for provisioning from a queue/console command, createRecoveryLinkForIdentity(...) for emailed password resets, deleteIdentitySessions($id) for "log out everywhere". For the self-service login/registration UI itself, don't use this SDK — drive those flows from the browser with @ory/client. PHP only validates the resulting session.
Testing: Configuration::setHttpClient() accepts a Guzzle client with a MockHandler, which is the cleanest way to fake Kratos. Alternatively bind a hand-written fake of your KratosClient wrapper in the container and keep API classes out of tests entirely.
Health checks: add MetadataApi::isReady() to your /health endpoint; it is cheap and catches Kratos outages before users do.
composer.json and upgrade it in lockstep with Kratos.Configuration::getDefaultConfiguration() is a static singleton. Mutating it in one place affects all later calls, which bites hard under Octane/long-running workers and in tests. Either clone it per use or keep a single immutable instance built in your provider.setAccessToken() is the admin bearer (ORY_SESSION_TOKEN); the frontend cookie/session token is a completely different value passed per call. Mixing them up produces confusing 401s.toSession can return active: false with HTTP 200 in some configurations, and 401 in others. Check $session->getActive() and catch ApiException; don't rely on the exception alone.ApiException. HTTP status is getCode(); the body is getResponseBody() — a JSON string with error.id (e.g. session_invalid, self_service_flow_expired) and error.message. Parse error.id for user-facing messaging rather than surfacing the raw body.traits, metadata_public, metadata_admin come back as stdClass/object, not typed models. Validate and cast them yourself; do not assume shape.timeout/connect_timeout via setHttpClient() for anything user-facing.LoggerPlugin or retry middleware onto the injected client; subclassing the generated *Api classes is fragile because they are regenerated.How can I help you explore Laravel packages today?