Authentication overview

Catentio doesn't have its own login system. We use Huudis — Forjio's shared identity provider — so you can use the same email and password (or Google / Apple account) across Catentio, Plugipay, Storlaunch, Fulkruma, LinkSnap, Pawpado, and any other Forjio product.

If you've signed up for a Forjio product before, you can sign in to Catentio with that same account — subject to the allowlist gate explained below.

One identity, many products. Your Huudis account is yours, not Catentio's. We don't store your password — Huudis does. We just trust the bearer tokens Huudis issues us when you sign in.

The allowlist gate

Catentio gates sign-in with an allowlist of Huudis identities, not an open signup. HUUDIS_ALLOWED_USER_ID (deploy-time env var) names the internal operator; an optional comma-separated HUUDIS_ALLOWED_USER_IDS adds specific customer users on top — this is how per-tenant customer onboarding works today, so "Catentio is single-user" is no longer accurate even though the gate traces back to the original single-user decision (ADR-0007 in the catentio repo). Every Huudis-authenticated user whose sub isn't in that set gets rejected with 403 not_authorized — there's no separate forbidden_user error code.

If you're not on the allowlist and you'd like to evaluate Catentio, contact us.

How sign-in works (the short version)

Email+password sign-in does not redirect you to Huudis. The portal's own login form POSTs your credentials straight to its /api/v1/auth/login route, which calls Huudis's OIDC token endpoint server-side (grant_type=password, i.e. Resource Owner Password Credentials), checks the returned identity against the allowlist, and sets your session cookie — all in one request, no visible redirect.

Google and Apple sign-in do use a redirect: the portal sends you to huudis.com/api/v1/oidc/authorize, Huudis authenticates you against the social provider, and redirects you back to catent.io/callback with a one-time code that the portal's callback route exchanges and gates the same way.

How sign-in works (the longer version)

Email + password

  1. The login form POSTs { email, password } to /api/v1/auth/login.
  2. The route calls Huudis's /api/v1/oidc/token with grant_type=password and the portal's own client_secret.
  3. It decodes the returned id_token's claims and checks sub against the allowlist (HUUDIS_ALLOWED_USER_ID + HUUDIS_ALLOWED_USER_IDS). No match → 403 { "error": { "code": "NOT_AUTHORIZED" } }.
  4. On a match, the portal HMAC-signs a session payload containing the Huudis tokens and sets it as an httpOnly, Secure cookie (catentio_session).

Google / Apple (OIDC authorization code + PKCE)

  1. Authorization request/api/v1/auth/huudis/start generates a random code_verifier, derives a code_challenge, stores both in a server-side PKCE cookie, and redirects you to Huudis with the challenge and an idp_hint for the chosen provider.
  2. User authentication — Huudis runs its provider's consent flow (or detects an active Huudis session) and returns a one-time authorization code.
  3. Redirect with code — Huudis redirects to catent.io/callback?code=…&state=…, and the client-side callback page POSTs {code, state} to /api/v1/auth/huudis/callback.
  4. Token exchange — That route POSTs the code (and the original code_verifier) to Huudis's token endpoint and gets access + refresh tokens.
  5. Allowlist gate — The callback decodes the access token's claims and rejects (403, JSON body, no redirect) anyone whose sub isn't on the allowlist.
  6. Session cookie — Same HMAC-signed catentio_session cookie as the password path.

The refresh token rotates on every use, with reuse detection: if Huudis sees the same refresh token presented twice, it treats it as a stolen-token signal and revokes the whole token family. Catentio implements a single-flight refresh cache to prevent this from triggering during normal browser polling.

Who uses which path?

Audience Auth path
You signing into the portal Password: direct ROPC POST. Google/Apple: OIDC code flow. Either way, a cookie session in the browser.
You, calling the API from a script you run yourself OIDC device flow. Same Huudis identity, different transport. See API authentication.
Server-to-server callers (a cron job, an integration) Static API key. Mint at Dashboard → API keys.
The runtime calling the control plane A signed service token (X-Catentio-CP-Token). Out of scope for these docs — internal.
The chat-bubble / live REPL session Inherits the portal cookie session.

The portal cookie and an API key are independent. Revoking one doesn't affect the other.

The Catentio session cookie (catentio_session) is a base64url payload signed with HMAC-SHA256 by the portal backend. It contains:

  • huudisAccessToken — the active access token, used to call Huudis APIs on your behalf.
  • huudisRefreshToken — used to mint new access tokens when the current one expires.
  • huudisUserId — your Huudis sub, used as the durable identifier and matched against the allowlist on every gated request.
  • accessExpAt — epoch millis when the access token expires (triggers proactive refresh).
  • customerId — the tenant this session routes to: internal for the operator identity, or the workspace's derived customer id for an allow-listed customer user.

The cookie is httpOnly (no JavaScript can read it) and Secure (HTTPS-only). It can't be inspected from the browser console.

The chat-bubble session model

Catentio's signature surface is the chat bubble — an embedded gojo REPL bound to your portal session. The bubble is what makes catent.io feel like an operator console rather than a database UI: every dashboard page has the bubble; you can ask it to "go to runs", "invoke hachimi with this", "summarise the last seven days of cost" without leaving the page.

The bubble's auth model is deliberately minimal: it sends the same catentio_session cookie the rest of the portal sends. There's no extra token, no Bearer header, no signed handshake from the client side. The server-side /api/v1/chat/* endpoints verify the cookie's HMAC and the allowlist gate, then forward to the runtime over the control plane's authenticated service channel.

This means the bubble works wherever your portal cookie is valid — same browser, same domain, same session. It also means signing out of the portal cleanly tears down the bubble.

Single sign-on across products

Because every Forjio product points at the same Huudis instance, you're already signed in to all of them once Huudis has an active session for you. Visit Plugipay after signing into Catentio — you skip the password screen.

You can sign out of one product without signing out of the others: each product owns its own session cookie. To sign out everywhere, click Sign out of all products in the Huudis identity portal.

What can go wrong

  • 403 not_authorized. You're a valid Huudis user, but your sub isn't on the deployment's allowlist (HUUDIS_ALLOWED_USER_ID / HUUDIS_ALLOWED_USER_IDS). There's no client-side fix — the deployment operator has to add you server-side.
  • Email not verified. If you signed up via email and didn't click the verification link, you can't sign in. Re-request from Huudis.
  • Forgot password. Catentio can't reset it — Huudis owns passwords. Follow the Forgot password flow.
  • Session expired. Cookies live for 7 days of inactivity. After that, you'll be sent back to /login on your next page load.

Next