Sign in with PeridotID
Let users sign into your app with their PeridotID (Google or passkey)
Add "Sign in with PeridotID" to any web app in three steps. Your users authenticate against PeridotID production — no local setup, no secrets to manage.
1. Register your app
Call POST /v1/apps with your PeridotID session (cookie) to get a public client_id.
List every URL PeridotID may return users to — codes are only issued to these:
import { Peridot } from '@peridotvault/pid-sdk-js';
const peridot = Peridot({ baseUrl: 'https://api.pid.peridotvault.com' });
// Register with just a name — allowed origins are managed after, in the dashboard
// at pid.peridotvault.com/workspace (or PATCH /v1/apps).
const res = await peridot.post('/v1/apps', { name: 'My Game' });
// res.data.clientId → "pidapp_..." — pass it as client_id in step 2Then add your websites under Allowed origins (bare https://… origins, no paths).
Pass the exact return URL in code — its origin must be listed:
Your redirect URI origins are automatically allowed for browser API calls (within
~60s of registering, no redeploy needed). Local development needs nothing at all:
http://localhost, http://127.0.0.1 and http://[::1] (any port, any path) are
always allowed — arbitrary production hosts are never accepted without registration
(an open redirect would let anyone steal users' login codes).
2. Open the login popup
npm install @peridotvault/pid-sdk-jsimport { Peridot, openLoginTab } from '@peridotvault/pid-sdk-js';
const peridot = Peridot({
baseUrl: 'https://api.pid.peridotvault.com',
solanaRpcUrl: 'https://api.devnet.solana.com',
popupBaseUrl: 'https://app.pid.peridotvault.com',
});
async function signIn() {
const { pidCode } = await openLoginTab({
popupBaseUrl: 'https://app.pid.peridotvault.com',
params: {
origin: window.location.origin,
popup: 'login',
client_id: 'pidapp_...',
},
});
if (pidCode) {
const identity = await peridot.auth.exchange(pidCode, 'pidapp_...');
// { pid, profile: { displayName, avatarUrl }, credentials: [{ provider, email }] }
}
}Auth opens the PeridotID login in a new tab (a full-page flow: Google, plus
the PID picker if the visitor has no PeridotID yet). When it completes the tab
mints a one-time pid_code for your client_id and posts it to your page, then
closes — you never handle a password and nothing runs in your DOM. Passkeys run
on the PeridotID origin (browsers bind WebAuthn to our domain); Google uses the
redirect round-trip. If the tab is blocked, fall back to navigating to the hosted
login directly (same query params). No API calls happen before login, so there is
nothing CORS-related to configure.
First sign-in creates a PID. If the visitor has no PeridotID yet, the tab shows the emission-free Create your PID step (the handle is permanent and user-chosen). Login only resolves once a PID exists.
popupBaseUrlis reused as the auth host; approval popups (a small window on the PeridotID origin) are used only for confirmations like transfers and payments.
In React, wrap the same two calls in your own button + hook — there is no provider component to install.
3. Handle the login
After login the tab returns a one-time pid_code (posted to your opener; a
full-page redirect_uri fallback is also supported). Exchange it as above —
directly, or via your backend, which then mints your app's own session.
Already signed in? The tab issues the code immediately (a one-tap consent screen
appears for cross-origin SSO). Denying returns with ?error=access_denied.
Prefer to mint your own session? Exchange server-side instead of calling
peridot.auth.exchange in the browser:
// your backend: verify the code against PeridotID, then create your session
const r = await fetch('https://api.pid.peridotvault.com/v1/auth/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, clientId: 'pidapp_...' }),
});
const identity = await r.json(); // single-use code, 5-minute expiryNotes
- Passkey login always runs on a PeridotID-hosted page (browsers bind WebAuthn to our
domain) and returns with
pid_codelike Google does. - Codes are single-use, expire in ~5 minutes, and are bound to your
client_id. - No framework? The snippet in step 2 is already framework-free:
openLoginTab(aliasopenLoginPopup),loginWithPasskey({ clientId, returnTo }),exchange(code, clientId).
Next: money
Once users can sign in, you can fund their balance, hold escrow on your app's account, and charge your own fee:
- Fiat & payments — top-up, balance, sends, escrow.
- Apps & workspace — manage your app, set per-app fees, read the escrow balance with a machine token.