Realsee Passport is the SSO that gates the developer portal. This page documents the sign-in handshake, the cookies involved, the CSRF protection, and how to detect login state from your own code.
When this matters
Passport is involved when you (the developer) sign in to Apps or use private Team data. Public documentation and the Five Demo remain available without portal sign-in. End-users of your application do not sign in through Passport. Browser-safe Five SDK flows receive only the scene data and public credentials supported by the integration, while OpenAPI credentials remain on your server. If you are building a public-facing integration, Passport never appears in your code.
The sign-in flow
Browser developer.realsee.ai login.realsee.ai app-gateway
β β β β
β GET /auth/sign-in?gotoURL=/dashboard/apps β β
β βββββββββββββββββββββββΆ β β
β β get source_verify ticket βββββββββββββββββββββββββββββΆβ
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββ-β
β 302 β login.realsee.ai/?source=...&source_verify=...&redirect=... β
β ββββββββββββββββββββββ β β
β β
β GET login.realsee.ai/?source=...&source_verify=...&redirect=... β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββΆ β
β (user signs in) β β
β 302 β /auth/sign-in-callback?source=...&ticket=...&login_type=... β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β GET /auth/sign-in-callback?source=...&ticket=... β β
β βββββββββββββββββββββββΆ β β
β β exchange ticket for token ββββββββββββββββββββββββββββΆβ
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββ-β
β β fetch user profile βββββββββββββββββββββββββββββββββββΆβ
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββ-β
β 302 β /dashboard/apps + Set-Cookie: realsee_token (httpOnly) β
β βββββββββββββββββββββββ β βThe five steps:
- Initiate β Sign-in starts at
/auth/sign-in(the dashboard's sign-in button links there; unauthenticated protected API calls return HTTP 401AUTH_REQUIREDrather than redirecting). The handler asks the internal app-gateway for a freshsource_verifyticket (proof the developer portal is allowed to ask for sign-in), then 302s tologin.realsee.ai/withsource,source_verify, andredirectparameters. ThegotoURLquery string captures where the user came from, so they land back there after sign-in. - Authenticate β The user signs in on Passport. Passport handles passwords, MFA, OAuth providers, and recovery flows β the developer portal sees none of it. On success, Passport issues a short-lived ticket and redirects back.
- Exchange β
/auth/sign-in-callback?source=...&ticket=...&login_type=...receives the ticket. The handler calls the app-gateway to exchange the ticket for a long-lived token, then calls the gateway again to fetch the user profile. Both calls happen server-side; the ticket never touches a long-lived store. - Set cookies β The handler issues two cookies on the response (outside production builds both names carry an environment suffix, e.g.
realsee_token_development):realsee_token(httpOnly, 30 days) β the session token. Sent on every subsequent request.realsee_account(httpOnly, 30 days) β the currently-selected Passport account, base64url-encoded and HMAC-SHA256-signed with the cookie secret. Remembers which account a multi-account user picked; the profile itself is still fetched from the gateway on each request.
- Redirect β The handler 302s the user to the original
gotoURL(or/dashboard/appsif missing). The cookie travels with them and/dashboardnow sees an authenticated session.
Cross-domain cookies
In production, the session cookies default to Domain=.realsee.ai, Path=/, SameSite=Lax, and Secure. Browsers can therefore attach them to matching HTTPS subdomains. Outside production they are host-only by default. Deployments can override either behavior with REALSEE_AUTH_COOKIE_DOMAIN.
The cookies are httpOnly, so browser JavaScript cannot read their values. That protection does not make a domain cookie host-only: the browser can still send it automatically to matching Realsee subdomains.
CSRF protection
Every mutating API endpoint on the developer portal (all are POST) is protected by a double-submit CSRF token plus an Origin/Referer check. The pattern:
- On
GET /api/me(the session probe the portal front-end calls on load), the server mints a random token (18 bytes, base64url-encoded) and sets it as thecsrfcookie (24-hour lifetime). This cookie is nothttpOnlyβ by design, so client JavaScript can read it. - On any mutating request, the client must:
- Send the
csrfcookie (automatic). - Echo the same value in the
x-csrf-tokenrequest header (manual β your fetch wrapper sets this). - If an
OriginorRefererheader is sent (browsers send one automatically), it must point at the developer portal host.
- Send the
The server compares the cookie to the header verbatim and rejects with HTTP 403 CSRF_TOKEN_MISSING when either value is absent or the two differ. Cross-origin attackers cannot read the cookie and cannot mint the matching header, so the check fails for them.
For implementation details see assertCsrf() and createCsrfCookieHeader() in src/server/http/request.ts; the client helper that echoes the header is src/client/csrf.ts. Failure codes you may encounter:
| Code | Meaning |
|---|---|
CSRF_TOKEN_MISSING | Cookie or header absent, or the two values differ. Call GET /api/me first to mint the cookie. |
CSRF_ORIGIN_MISMATCH | An Origin or Referer header was sent but does not match the developer portal host. |
Detecting login state
Server-side, protected API handlers call requireDeveloperContext() from src/server/http/request.ts. It resolves the Passport session and distinguishes Passport unavailability, an anonymous visitor, and a signed-in user without an active Team. Client-side, you can probe login state with a simple fetch:
async function isSignedIn(): Promise<boolean> {
const res = await fetch('/auth/get-user', { credentials: 'include' })
if (!res.ok) return false
const body = await res.json()
return body?.data?.isAuthenticated === true
}/auth/get-user never mutates state. It responds HTTP 200 with a { data: ... } envelope for both anonymous and authenticated sessions, and HTTP 503 when Passport is unavailable. When signed in, data is the developer account (userCode, isAuthenticated: true, email, name, avatarUrl, currentSource, teams); the raw Passport UserID is not exposed. When signed out, it is { isAuthenticated: false, teams: [] }. GET /api/me returns the same session shape and additionally mints the CSRF cookie.
To sign out: send the user to /auth/sign-out (GET or POST) β the server clears both session cookies and 302s to the gotoURL query parameter (default /docs). It does not round-trip through Passport, so only the developer portal session is ended.
Compatibility routes
The portal preserves several legacy URLs as 302 redirects for backwards compatibility:
| Legacy path | Canonical target |
|---|---|
/login | /auth/sign-in |
/logout | /auth/sign-out |
/signup | /auth/sign-up |
/register | /auth/sign-up |
/login/callback | /auth/sign-in-callback |
/logout/callback | /auth/sign-out-callback |
/signup/callback | /auth/sign-up-callback |
Query strings are preserved, so existing deep links with ?gotoURL=... continue to work.
Open redirect protection
The gotoURL parameter (the auth routes also accept redirect_to and next as aliases) is validated server-side before any redirect: it must start with / and must not start with // (protocol-relative). A crafted ?gotoURL=https://evil.example/phish falls back to the safe default /dashboard/apps. The check lives in safeGotoPath() in src/server/auth/cookies.ts.
Next steps
- Authentication overview β the three-layer model.
- Team and App Management β create a trial app and keep its Completed request bound to the AK.
