> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peridot.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Google sign-in, cookies, refresh rotation, and logout

PeridotID authentication is **cookie-based**. After a successful Google sign-in the API
sets two `HttpOnly` cookies on your domain.

## The two cookies

| Cookie | Purpose | Lifetime | Path |
| - | - | - | - |
| `pid_access` | JWT access token, sent with every request | 15 minutes | `/` |
| `pid_refresh` | Rotating refresh token, only used to mint new access tokens | 30 days | `/v1/auth` |

Both are `HttpOnly; SameSite=Lax`. In production the `Secure` flag is also set, so they
are only sent over HTTPS.

## Sign-in flow

```text theme={null}
Browser                    PeridotID                 Google
   │  POST /v1/auth/login     │                        │
   │  ← {"url": "/v1/auth/google"}                     │
   │  GET  /v1/auth/google ──►│  302 ─────────────────►│  consent screen
   │  ◄──────────────────────  │                        │
   │  ◄───────────────────────────── 302 + auth code ──  │
   │  GET  /v1/auth/google/callback?code=...            │
   │                        │  exchange code, create or link identity
   │  ◄─ 302 CLIENT_SUCCESS_URL + pid_access + pid_refresh cookies
```

1. Call `POST /v1/auth/login` (or use the SDK) to get the Google authorization URL.
2. Navigate the browser to that URL. The user consents on Google.
3. Google redirects to `GET /v1/auth/google/callback?code=...`. The API exchanges the
   code and resolves the credential.
4. Returning credential → session cookies are issued and the browser lands on
   `CLIENT_SUCCESS_URL` with a logged-in session. New credential → the PID picker
   (see below).

On a first-time sign-in the API mints a single-use claim ticket
(`pid_claim` cookie, 10 min) and redirects to `CLIENT_SUCCESS_URL?claim=1`. The claim
screen shows who you're continuing as — pick a handle, `POST /v1/auth/claim`, and the
identity (`<handle>@pid`), profile, and credential are created in one transaction.
Nothing exists until you claim: abandoning restarts cleanly on the next login, and an
expired ticket answers `410`. On every later
sign-in the same Google credential is found and you land back on the **same identity** —
the PID never changes, is never reused, and cannot be reassigned, no matter which
providers you link or unlink later. **Choose the handle carefully: it is permanent.**

> **Note:** `GET /v1/auth/google` and `/v1/auth/google/callback` are **browser-navigation only**.
> They respond with `302` redirects that a `fetch()`/Swagger "Send" call cannot follow
> (the cross-origin redirect to Google is CORS-blocked, showing `Failed to fetch`). Always
> open the login URL in a browser tab — never call these two endpoints from JavaScript.

## Login credentials

Every way to log in — Google, email OTP, passkeys (Apple coming soon) — is a
**credential**. Credentials belong to an identity:

* The pair `(provider, providerUserId)` is unique — it is the source of truth.
* A non-null `email` is **unique across accounts**: one email = one PID. If a new login
  presents an email already associated with another account, it is rejected with `409`
  (`Email sudah terhubung dengan akun lain`) — accounts are never merged, moved, or minted
  twice (see [Errors](/errors)).
* The identity (PID) is the only source of truth. Credentials are just the keys that prove
  ownership of a PID.

List and unlink credentials via the SDK:

```ts theme={null}
const credentials = await peridot.identity.credentials();
// [{ id, provider: "google", email, linkedAt, lastLoginAt }]

await peridot.identity.unlinkCredential(credentials[0].id);
```

An identity must always keep **at least one credential** — unlinking the last one returns
`400`. This prevents a user from locking themselves out. Unlinking a credential never
touches the identity or its wallet (if any) — the wallet belongs to the PID, not to a
provider.

## Email OTP login

`POST /v1/auth/email/start` sends a 6-digit code to the address (Brevo sender,
10-minute TTL, 60-second resend cooldown, 5 requests/hour per address), then
`POST /v1/auth/email/verify` consumes it (single-use, locked after 5 wrong
attempts). Returning addresses land straight on a session; new addresses get a
claim ticket and continue through the same PID picker as Google. The code itself
is never stored — only its SHA-256 hash — and both endpoints answer generically
so they never reveal whether an address has an account.

```ts theme={null}
await peridot.auth.emailStart("ifal@example.com");
const res = await peridot.auth.emailVerify("ifal@example.com", "428311");
if (res.pendingClaim) {
  await peridot.auth.claim("ifal"); // new address → pick a handle
}
```

## Refreshing tokens

The access token expires after 15 minutes. Instead of asking the user to log in again,
call `POST /v1/auth/refresh`. It:

1. Verifies the current `pid_refresh` cookie.
2. **Revokes it** (rotation) and issues a new access + refresh pair as cookies.

Because each refresh token can be used exactly once, a stolen token that is replayed after
rotation is rejected.

```bash theme={null}
curl -b cookies.txt -c cookies.txt -X POST https://api.pid.peridotvault.com/v1/auth/refresh
# 204 No Content — new cookies set, old refresh token revoked
```

If this returns `401`, the refresh token is invalid, expired, or already rotated — the
user must sign in again.

## Logging out

`POST /v1/auth/logout` revokes the current refresh token and clears both cookies. It is
idempotent — safe to call when already logged out.

```bash theme={null}
curl -b cookies.txt -c cookies.txt -X POST https://api.pid.peridotvault.com/v1/auth/logout
```

## Security notes

* Access tokens are short-lived; the real secret is the rotating refresh token.
* The refresh cookie's `Path=/v1/auth` means it is only sent to auth endpoints, reducing
  the window for cross-route exfiltration.
* Auth endpoints are rate limited (see [Errors](/errors)).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.