---
title: PHP SDK quickstart
---

# PHP SDK (`littlexlittle/id-php`)

Two delivery modes:

1. **Composer** — `composer require littlexlittle/id-php`.
2. **Single file** — drop [`cdn/sdk/php/lxl-id.php`](https://id.littlexlittle.org/sdk/php/lxl-id.php) into your project. Same API, no autoloader needed.

PHP 7.4+ supported. Uses `firebase/php-jwt` for signature verification (vendored in single-file mode).

## Install

=== "Composer"

    ```bash
    composer require littlexlittle/id-php
    ```

    ```php
    require 'vendor/autoload.php';
    use LXL\Id\Client;
    ```

=== "Single file"

    ```bash
    curl -o lxl-id.php https://id.littlexlittle.org/sdk/php/lxl-id.php
    ```

    ```php
    require __DIR__ . '/lxl-id.php';
    use LXL\Id\Client;
    ```

## 1. Configure

```php
$client = new Client([
    'client_id'     => 'YOUR_CLIENT_ID',
    'client_secret' => 'YOUR_CLIENT_SECRET',  // omit for public clients
    'redirect_uri'  => 'https://yoursite.org/auth/callback',
]);
```

## 2. Start the auth flow

```php
$url = $client->createAuthUrl([
    'scope' => 'openid profile email lxl.access',
    'state' => bin2hex(random_bytes(16)),       // CSRF token
]);
header('Location: ' . $url);
exit;
```

For public clients, the SDK auto-generates and stores the PKCE `code_verifier` in the session.

## 3. Handle the callback

```php
// auth/callback.php
session_start();
$tokens   = $client->fetchAccessTokenWithAuthCode($_GET['code']);
$payload  = $client->verifyIdToken($tokens['id_token']);  // throws on bad signature
$userInfo = $client->fetchUserInfo($tokens['access_token']);

$client->loginUser($payload, $tokens);  // sets HttpOnly session cookie
header('Location: /');
```

## 4. Verify a One-Tap credential

When using the JS SDK in One-Tap mode, your callback POSTs a JWT directly:

```php
$payload = $client->verifyOneTap($_POST['credential']);
echo "Hi {$payload['name']} ({$payload['sub']})";
```

## 5. Check permissions on subsequent requests

```php
use LXL\Id\Auth;

if (!Auth::isLoggedIn()) {
    header('Location: /login');
    exit;
}
if (!Auth::access('Website', 'Media')) {
    http_response_code(403);
    exit('forbidden');
}
```

`Auth::access()` reads the `lxl.access` claim that was minted into the session — no API call.

## 6. Refresh in the background

```php
if ($tokens['expires_at'] < time() + 60) {
    $tokens = $client->refresh($tokens['refresh_token']);
}
```

The SDK rotates the refresh token automatically and stores the new one. Re-using a rotated refresh token revokes the entire chain (replay detection per RFC 6749 §10.4).

## 7. Sign out

```php
$client->logout([
    'id_token_hint'            => $tokens['id_token'],
    'post_logout_redirect_uri' => 'https://yoursite.org/',
]);
```

## Public API

| Method | Purpose |
|---|---|
| `createAuthUrl($params)` | Build the `/oidc/authorize` URL with PKCE if needed. |
| `fetchAccessTokenWithAuthCode($code)` | Exchange code → tokens. |
| `verifyIdToken($jwt)` | Verify signature + claims. Returns claims array. |
| `fetchUserInfo($accessToken)` | GET `/oidc/userinfo`. |
| `verifyOneTap($credential)` | One-call verifier for the One-Tap callback. |
| `refresh($refreshToken)` | Rotate refresh token and get new access/id tokens. |
| `revoke($token)` | RFC 7009 revoke. |
| `logout($params)` | RP-Initiated Logout. |
| `loginUser($payload, $tokens)` | Set session cookie and persist tokens. |
| `Auth::isLoggedIn()` / `Auth::access($s, $sub)` | RBAC helpers. |

## JWKS caching

The SDK caches JWKS to `__DIR__ . '/cache/jwks.json'` for 6 hours by default. Override via:

```php
$client = new Client([
    'client_id' => '...',
    'jwks_cache_dir' => '/var/cache/lxl-id',
    'jwks_ttl' => 3600,  // seconds
]);
```

Stale-while-revalidate: if fetch fails, the SDK keeps using the cached JWKS until it succeeds.

## Next

- [Refresh tokens deep dive](../guides/refresh-tokens.md)
- [Webhook signature verification](../guides/webhooks.md)
- [Error codes](../reference/errors.md)
