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 only when you (the developer) sign in to /dashboard. 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/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 β submit an App request and receive provisioned credentials.
