WebApps Authentication

Document information
  • Canonical URL: /docs/05_apps-webhooks-and-surfaces/webapps/46-Webapps_Authentication
  • Version: 2026-08-13
  • Tags: webapps, authentication, jwt, guides

Published custom WebApps (type: webapp) authenticate end users with a token issued by your login ProcessFlow. Process execution still runs as the configured webapp_user_id service account. End-user identity lives in the token claims and in process_input.auth / process_input.auth_token.

Do not call product POST /api/v1/auth/login from a WebApp login snippet. That issues a product type: access token for the console API, not an end-user WebApp token.

GET HTML for the published page stays public. Page JavaScript should gate UI. Runtime auth applies to POST and /api/* under the WebApp host.

Choose an auth transport

Set auth_transport in WebApp Settings (or POST/PUT /api/v1/webapps).

ValueLogin responseClient sends laterCookie rewrite
cookie (default)Set-Cookie: tf_session (HttpOnly, Secure, SameSite=Lax)CookieYes
bearerJWT in JSON (access_token)Authorization: BearerNo
bothCookie and JSON bodyBearer preferred, cookie fallbackYes

Use Cookie for same-origin HTML forms. Use Bearer for custom JavaScript apps (including cross-origin callers). Use Both only while migrating; JavaScript can still read the JSON copy of the token, so it is not XSS-resistant.

CORS on the published runtime keeps Access-Control-Allow-Credentials: false. Cross-origin clients must use Bearer. Do not expect cookies to work across origins.

Login JSON contract

Login stays on the JSON WebApp POST (the published page POST), not /api/*.

const email = String(process_input.email ?? "").trim();
const password = String(process_input.password ?? "");
// App-defined user lookup (tenantDb / integration). Do not call product /auth/login.
// If persisting users: store a one-way password hash only — never the raw password.
// Do not log process_input or password.

const issued = jwt.issueWebappAccess({
  user_id: endUserId,
  email,
  role: "member"
});

return {
  success: true,
  access_token: issued.access_token,
  token_type: "Bearer",
  expires_at: issued.expires_at,
  data: { authenticated: true, user_id: endUserId }
};

jwt.issueWebappAccess binds tenantId and webapp_id from the executing WebApp. It always sets type: webapp_access and signs with WEBAPP_JWT_SECRET. There is no generic jwt.sign.

For cookie or both, the same access_token field still triggers Set-Cookie via the runtime. For bearer, the token stays in the JSON body only.

Recommended client storage for Bearer tokens: in-memory. localStorage is readable by any XSS on the page.

Later requests:

POST /{webapp_id}
Authorization: Bearer <jwt>
Content-Type: application/json

The runtime verifies JWT-shaped tokens before execute and injects:

  • process_input.auth_token — raw token
  • process_input.auth{ user_id, email, role, webapp_id, type } when the token is a valid webapp_access JWT

On /api/* for api_router processes, process_input.token is still set for compatibility. Query ?token= is not used for webapp_access. It remains only when the api_router process config token_types includes document_review.

Require authentication

auth_require (default false) returns 401 on POST and /api/* when no token is present.

Keep auth_require off on login WebApps that issue tokens. Turn it on for APIs and pages that assume the user is already signed in.

Invalid, expired, revoked, or wrong-webapp JWTs also return 401 and the process does not run. Opaque (non-JWT) tf_session values still pass through in cookie mode for existing apps. Opaque Bearer is not supported.

A webapp_access token cannot call product /api/v1/* routes. Product guards reject type: webapp_access. A token issued for WebApp A cannot be used on WebApp B.

Logout

  • Bearer: drop the in-memory token. Optionally return clear_auth_cookie: true if you also used cookies.
  • Cookie / both: return clear_auth_cookie: true from the logout process. The runtime clears tf_session and revokes the presented JWT jti when present.

Passwords and tokens in logs

DataStore in the databaseLogs / execution history / debug
User-submitted password (including passwd, current_password, new_password)Never in clear text. Hash with a one-way password hash inside the snippet if you persist users.Never in clear text. Always "****".
access_token / session_token / refresh_token / auth_token / AuthorizationDo not store full JWTs in WebAppExecutionLogs or process history. App-owned session tables may store hashes or jti.Masked only (prefix/suffix).
Email / usernameAllowedAllowed

Do not tenantDb.insert raw passwords. Do not console.log login payloads. Compare hashes; never log process_input.password.

Related guides