Olympus Docs
CookbookEnterprise SSO

Just-in-time user provisioning

Auto-create user records in your app on first Olympus login

JIT provisioning: when a user logs in via Olympus for the first time, your app creates their corresponding record. No pre-staging needed.

Pattern

1. User authenticates with Olympus (existing identity).
2. Your app receives the ID token.
3. Your app checks: does my DB have a row for this identity_id?
   - Yes → continue.
   - No → create the row (default settings, free tier, etc.).
4. Continue request normally.

Implementation

async function getOrProvisionUser(idToken: { sub: string; email: string }) {
  const existing = await db`
    SELECT * FROM users WHERE olympus_sub = ${idToken.sub}
  `.first();

  if (existing) return existing;

  // JIT provisioning
  const newUser = await db`
    INSERT INTO users (olympus_sub, email, plan, created_at)
    VALUES (${idToken.sub}, ${idToken.email}, 'free', NOW())
    RETURNING *
  `.first();

  // Side effects on first signup
  await sendWelcomeEmail(newUser.email);
  await createDefaultResources(newUser.id);
  await analyticsTrack("signup", { source: "jit", email: newUser.email });

  return newUser;
}

Call from your auth middleware after token validation.

Race conditions

Two simultaneous logins from the same Olympus identity can both miss the existing row and try to insert. Use a unique constraint and handle the conflict:

INSERT INTO users (olympus_sub, email, plan)
VALUES ($1, $2, 'free')
ON CONFLICT (olympus_sub) DO UPDATE SET
  email = EXCLUDED.email   -- update email if changed
RETURNING *;

What if the email changed in Kratos?

Some users update their email. Your local record should track:

  • As the user record's primary identifier: use olympus_sub (immutable UUID), not email.
  • Email: store but allow updates from Kratos. Sync on every login.
if (existing.email !== idToken.email) {
  await db`UPDATE users SET email = ${idToken.email} WHERE olympus_sub = ${idToken.sub}`;
}

Triggered alternatives

Instead of JIT on login, you can:

  • Kratos webhook: on after.registration.password.hooks, send your backend a webhook. Your backend provisions immediately. See Cookbook, Custom Kratos webhook.

Trade-off: webhook fires once on registration; JIT runs on every login (free correctness check). JIT is more resilient.

What to provision

  • App-side user row.
  • Default settings.
  • Free-tier resource quotas.
  • Welcome notification.

What NOT to provision automatically:

  • Paid resources (charge them first).
  • Permissions to others' data (security risk).
  • External provider accounts (e.g. don't auto-create Stripe customer unless they paid).

On this page