HttpOnly cookies on your domain.
The two cookies
Both are
HttpOnly; SameSite=Lax. In production the Secure flag is also set, so they
are only sent over HTTPS.
Sign-in flow
- Call
POST /v1/auth/login(or use the SDK) to get the Google authorization URL. - Navigate the browser to that URL. The user consents on Google.
- Google redirects to
GET /v1/auth/google/callback?code=.... The API exchanges the code and resolves the credential. - Returning credential → session cookies are issued and the browser lands on
CLIENT_SUCCESS_URLwith a logged-in session. New credential → the PID picker (see below).
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/googleand/v1/auth/google/callbackare browser-navigation only. They respond with302redirects that afetch()/Swagger “Send” call cannot follow (the cross-origin redirect to Google is CORS-blocked, showingFailed 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
emailis unique across accounts: one email = one PID. If a new login presents an email already associated with another account, it is rejected with409(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.
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.
Refreshing tokens
The access token expires after 15 minutes. Instead of asking the user to log in again, callPOST /v1/auth/refresh. It:
- Verifies the current
pid_refreshcookie. - Revokes it (rotation) and issues a new access + refresh pair as cookies.
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.
Security notes
- Access tokens are short-lived; the real secret is the rotating refresh token.
- The refresh cookie’s
Path=/v1/authmeans it is only sent to auth endpoints, reducing the window for cross-route exfiltration. - Auth endpoints are rate limited (see Errors).