@incorta/auth
v1.9.0
Published
Authenticate Incorta users in any Node.js app — OAuth 2.0/OIDC against Incorta's built-in authorization server, with a React provider and Express/TanStack Start integrations.
Downloads
1,452
Keywords
Readme
@incorta/auth
Authenticate Incorta users in any Node.js app via Incorta's built-in OAuth 2.0/OIDC authorization server. See the repository root README for the full guide; this is the API surface.
To read Incorta with the session this package holds — schemas, tables,
columns, rows, all filtered to the signed-in user — add
@incorta/sdk, released from this repository at the same
version.
Entry points
| Import | What's in it |
| ----------------------- | ------------------------------------------------------------------- |
| @incorta/auth | createIncortaAuth, registerClient, getClient, updateClientRedirectUris, deleteClient, types (IncortaUser, AuthSession, …) |
| @incorta/auth/react | IncortaAuthProvider, useAuth, useUser, SignedIn, SignedOut, Protect, RedirectToLogin |
| @incorta/auth/express | incortaAuth(auth) middleware, requireAuth(auth, options?) |
| @incorta/auth/node | toNodeHandler(auth), toWebRequest, sendWebResponse, getNodeSession |
createIncortaAuth(config?)
Returns { handler, getSession, config }:
handler(request: Request): Promise<Response>— serves, underconfig.basePath(default/auth):GET /login?redirect_to=— 302 into the Incorta authorize flow (or straight back when already signed in), with a PKCE S256 challenge. Sets a sealed state+nonce+verifier cookie (10 min).GET /callback— validates state, exchanges the code with the PKCE verifier (as a public client —client_idin the body — or withclient_secret_basicwhen a secret is configured), verifies the ID token against the tenant JWKS (issuer, audience, nonce), seals the session cookie, 302 to the original page. Failures 302 to{redirect_to}?incorta_auth_error={code}.GET /session—{ user, session }or{ user: null }. Refreshes the access token (rotating refresh token) when <60s from expiry; slides the session cookie past its half-life.GET|POST /logout— clears the session cookie (GET redirects: toredirect_towhen given, else to/signed-out).GET /signed-out— ungated "you're signed out" page with a sign-in link; the reactsignOut()default destination. Landing anywhere gated would silently re-login via SSO while the Incorta session lives.GET /token— the raw Incorta access token; only withexposeAccessToken: true, otherwise 404.
getSession(request): Promise<AuthSession | null>— read-only session lookup for server code (user,accessToken,accessTokenExpiresAt,expiresAt).
Config falls back to INCORTA_URL, INCORTA_TENANT, INCORTA_CLIENT_ID,
INCORTA_AUTH_SECRET, INCORTA_APP_URL, INCORTA_CLIENT_SECRET (optional —
only a confidential client has one; Incorta registers apps as public
clients), and
INCORTA_INTERNAL_URL (split horizon: server-side discovery/token/JWKS calls
use it when the public incortaUrl is not routable from the app — e.g.
http://host.docker.internal:8080/incorta from a local container; the
authorize redirect and issuer validation stay on the public URL).
App access policy (appAccess / INCORTA_APP_ACCESS)
Who may use the app, enforced by the whole-app gate:
public— anyone with the URL, signed in or not. The gate is inert for app routes; the auth routes stay mounted, so signing in remains possible and sessions become optional (handlesession == null).authenticated(default) — any signed-in Incorta user. Exactly the pre-policy behavior, and what an absent/unknownINCORTA_APP_ACCESSmeans (unknown values warn and fall back here — never topublic).catalog— signed in AND at least VIEW access to this app in the Incorta catalog. The SDK asksGET {incortaUrl}/apps-catalog/checkwith the session's own access token; Incorta resolves which app the token belongs to from the OAuth client registration's(incorta-app:<id>)name suffix (written once, server-side, at registration), so the endpoint can only ever answer about this app. Verdicts (allow and deny) are cached 30s per user; apps not registered in the catalog stay open to authenticated users (legacy-open). When Incorta cannot answer, the gate fails CLOSED with a distinct 503 "temporarily unavailable" page (failures are not cached).
Enforcement surfaces:
auth.gate(request): Promise<Response | null>— the whole-app gate: serve theResponsewhen non-null (auth routes, login redirects, the 403 access-denied page — which names the signed-in user and offers a switch-account logout — or the 503 page), continue to your app onnull.@incorta/auth/express:appGate(auth)middleware isauth.gatefor Express/Connect.requireAuth(auth)always demands a session regardless of policy (an explicit route guard is the author's promise), and undercatalogadditionally requires VIEW, attaching the verdict asreq.incortaAuth.appAccess.auth.getAppAccess(session): Promise<AppAccess>— the cached verdict (registered,accessbitmask,canView,canEdit,allowed) for permission-aware UX in any mode.
GET {basePath}/refresh?redirect_to= supports the catalog gate: it refreshes
an expired access token through a browser round trip (persisting the rotated
refresh-token cookie) and bounces back — the gate sends navigations there
instead of calling Incorta with a token it knows is expired.
Session model
The cookie is an encrypted JWT (JWE dir/A256GCM, key derived from
secret), HttpOnly, SameSite=Lax, Secure on HTTPS. Payload: verified user
claims (sub, name, email, roles, tenant) + the Incorta access/refresh
tokens. Nothing is stored server-side.
Embedded (cross-site iframe) mode: when the app runs inside an iframe on a
DIFFERENT site, Lax cookies are never sent — the login flow loops forever.
Set cookies: { sameSite: "none", partitioned: true } (or env
INCORTA_COOKIE_SAMESITE=none + INCORTA_COOKIE_PARTITIONED=1): the auth
cookies become SameSite=None; Secure; Partitioned (CHIPS), which modern
Chrome allows in third-party iframes with a per-embedder cookie jar. none
forces Secure (accepted on trustworthy plain-http origins like
*.localhost); top-level usage keeps working with the same setting.
CLI
incorta-auth register --url http://localhost:8080/incorta --tenant demo \
--name "My App" --redirect https://myapp.example.com/auth/callback [--json]
incorta-auth show --url ... --tenant ... --client <id> --rat <token> [--json]
incorta-auth update-redirects --url ... --tenant ... --client <id> --rat <token> \
--redirect <url> [--redirect ...] [--replace] [--json]
incorta-auth delete --url ... --tenant ... --client <id> --rat <token>register creates the OAuth client via RFC 7591 dynamic registration and
prints the .env entries (keep the registration access token). show and
update-redirects manage the registered client's allowed callback URLs —
adding by default, replacing the whole set with --replace. delete removes
the registration (new logins stop immediately; issued sessions live until
they expire).
Testing
pnpm test runs the suite against an in-process replica of Incorta's OAuth
endpoints (test/mock-incorta.ts) — full login/callback/refresh/logout flows
over real HTTP sockets, no Incorta needed.
