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

CookiePurposeLifetimePath
pid_accessJWT access token, sent with every request15 minutes/
pid_refreshRotating refresh token, only used to mint new access tokens30 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

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, Discord, Apple, email+password, passkeys — 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).
  • 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:

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.

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.

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.

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).

On this page