Skip to content

Anonymous sign-ins

Rakomi supports anonymous sign-ins — a short-lived guest identity with no email, no password, and a server-assigned opaque UUID. Use it when you want users to try your app before they commit to creating an account; later they can claim the guest identity by registering, and their data (cart, preferences, metadata) carries over because users.id is preserved.

Guest vs. anonymous. Rakomi uses “anonymous” in the API and JWT claim (is_anonymous: true) to match Firebase, Supabase, and Cognito parity. The user-facing term is guest — an opaque, time-bounded identity.

ProviderAPIUpgrade path
FirebasesignInAnonymously()linkWithCredential()
SupabaseAnonymous sign-ins (GA)Update user with email
CognitoGetIdMergeDeveloperIdentities
RakomiPOST /v1/auth/anonymousRegister with anon bearer → claim preserves users.id

Enterprise-only providers (Stytch, WorkOS) do not offer a first-class anonymous path. Rakomi supports guest sessions for consumer and B2C workloads while keeping the same tenant-isolated, cross-tenant-safe posture.

Anonymous sign-ins are disabled by default on every new tenant. Enable them in the dashboard:

  1. Settings → Authentication → Anonymous.
  2. Toggle “Enable anonymous sign-ins” ON.
  3. Choose an inactive-user retention period (1–90 days, default 30).

If your tenant’s default role grants permissions beyond read-only, the dashboard surfaces a red warning banner. Enabling the feature the first time requires an explicit acknowledgement (soft-confirm with the X-Rakomi-Confirm: privileged-default-role header — the dashboard wires this for you). Anonymous users inherit the default role until they register, so the recommendation is to keep the default role viewer-only.

import { AnonymousSessionExpiredError, RakomiClient } from '@rakomi/node';
const client = new RakomiClient({ apiKey: process.env.RAKOMI_API_KEY! });
const result = await client.anonymous({
publicMetadata: { cart_id: 'c_123' },
});
if (!result.ok) {
console.error(result.error.code); // anonymous/disabled | anonymous/rate_limited | anonymous/mau_exhausted
return;
}
console.log(result.data.user.id); // opaque UUID
import { useAnonymousSignin, useAuth } from '@rakomi/react';
export function GuestCTA() {
const { signIn, isLoading, error } = useAnonymousSignin();
const auth = useAuth();
if (auth.isLoaded && auth.isSignedIn && auth.user.isAnonymous) {
return <p>You're a guest. <a href="/register">Create an account</a> to save your work.</p>;
}
return (
<button disabled={isLoading} onClick={() => signIn({ publicMetadata: { source: 'cta' } })}>
Try it out
</button>
);
}

Security note. useAuth().user.isAnonymous is derived from the verified JWT. Never infer “anonymous” from URL parameters, localStorage, or any untrusted client state — those paths are forgeable.

Terminal window
curl -XPOST https://api.rakomi.com/v1/auth/anonymous \
-H 'Content-Type: application/json' \
-H "X-API-Key: $RAKOMI_API_KEY" \
-d '{}'
# 201 Created → { access_token, refresh_token, expires_in, user: { id, is_anonymous: true, created_at } }
create → use → (refresh)* → claim → registered user
│ │
└─── no activity (TTL days) ──────────┴── purge (hard delete)
  • CreatePOST /v1/auth/anonymous. Returns a JWT with is_anonymous: true, aal: "AAL1".
  • Use — authenticate as any other user. /v1/auth/me works with the anon bearer.
  • RefreshPOST /v1/auth/refresh rotates the token. is_anonymous: true survives rotation.
  • ClaimPOST /v1/auth/register with the anon bearer sets the email + password and flips is_anonymous: false. The users.id value is preserved, so all dependent rows carry over.
  • Purge — a daily cron at 02:30 UTC hard-deletes anonymous users whose last_active_at is older than the tenant’s retention setting.

An anonymous user is like a hotel walk-in with a temporary room key. Check-in at reception (register) turns the walk-in into a named guest without re-issuing room access. The guest’s belongings (metadata, cart) stay in the same room.

Disabling the toggle affects new sessions only

Section titled “Disabling the toggle affects new sessions only”

Turning off “Enable anonymous sign-ins” stops new guest creations. Existing guest tokens continue to refresh until they expire naturally or the purge cron removes the row at TTL. If you need to cut existing guests immediately, the operator user-deletion tool is the path.

Lowering TTL aggressively drains gradually

Section titled “Lowering TTL aggressively drains gradually”

The purge cron caps at 1 000 rows per tenant per night to protect DB throughput. Lowering retention from 90 → 1 with 50 000 dormant guests drains over ~50 nights. Plan accordingly.

  • Per-IP: 5 requests per minute (same as /v1/auth/register).
  • Per-API-key: 1 000 requests per hour — defense in depth against rotating proxies. 429 responses include Retry-After and X-Rakomi-Docs: https://docs.rakomi.dev/reference/auth/anonymous#rate-limits.
  • Anonymous tokens participate in normal JWT key rotation transparently.
  • A refresh failing with 401 on a previously-anon token throws AnonymousSessionExpiredError in the Node SDK (with suggestedAction: 'call_anonymous()') so you can mint a fresh guest session instead of blanking the user out.

The guest session cookie is a strictly-necessary authentication cookie — no cookie-consent prompt is required under the current ePrivacy Directive interpretation. The ROPA entry anonymous_user_creation documents the legal posture.

The purge cron is not business-critical under ISO 27001 A.5.30. Missing a run for ≤ 48 hours has no regulatory implication — the next successful run drains the backlog. This surface is in scope for the quarterly LAB security re-scan.

PathStatus
Email + passwordSupported (Story 21.4b)
Social (OAuth)Supported (Story 21.4-claim-social) — 10 providers
PasskeyFuture work

A guest who started their session with POST /v1/auth/anonymous can upgrade to a full account by signing in with a social provider. Send the anonymous bearer token on the /oauth/{provider}/authorize initiate call; the server persists the anon user id across the provider redirect and, on the callback, updates the SAME users.id in place (preserving all FK-keyed data — sessions, metadata, org memberships, audit trail).

Supported providers: google, apple (email always_verified); github, microsoft, discord, slack, gitlab, linkedin (provider_verified); facebook, twitter (never_trust — these do not auto-link to an anon row; they create a fresh account instead, matching Auth0/Firebase anti-takeover semantics for unverified provider emails).

import { RakomiClient } from '@rakomi/node';
const client = new RakomiClient({ apiKey: process.env.RAKOMI_API_KEY! });
// 1. Create guest session
const guest = await client.auth.anonymous();
// 2. Redirect user's browser to the initiate route with the anon bearer.
// The browser will follow 302 → Google → 302 back to /oauth/google/callback.
// After the callback, the returned tokens identify the SAME users.id as `guest`,
// but with is_anonymous: false and the Google federated identity linked.
const initiateUrl = `https://accounts.rakomi.com/oauth/google/authorize?tenant_id=${tenantId}&redirect_uri=${redirectUri}`;
// browser-side fetch with `Authorization: Bearer ${guest.access_token}` header

Error redirects. Three Rakomi-extension error codes may land in the ?error= fragment on the callback redirect: already_claimed (the anon row was already claimed in parallel), email_exists (the email returned by the provider belongs to a different account), provider_in_use (this social account is already linked to another Rakomi user). Localize these in your accounts app (see Story 21.3 i18n keys).

My guest session disappeared — what happened? The tenant’s inactive-user retention period lapsed. Retention is configured in Settings → Authentication → Anonymous.

I got 429 on /v1/auth/anonymous — why? Either the per-IP (5/min) or per-API-key (1 000/h) cap was hit. The response carries Retry-After; back off accordingly.

Can I reset and start fresh? Yes — on the browser, clear the session cookie / local tokens and call client.anonymous() again. This creates a new guest with a new UUID.

Can a guest user belong to an organization? No. Organization membership requires a claimed identity.

What if an API key used to create the guest is revoked later? The guest token remains valid for its exp. Refresh will fail because the session’s API key is revoked — the user is signed out naturally.

What identifier is attached to me as a guest? An opaque UUID. No IP or User-Agent is stored on the user row. Server logs may retain short-lived access records per platform retention policy, same as any authenticated request.