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).
| Value | Login response | Client sends later | Cookie rewrite |
|---|---|---|---|
cookie (default) | Set-Cookie: tf_session (HttpOnly, Secure, SameSite=Lax) | Cookie | Yes |
bearer | JWT in JSON (access_token) | Authorization: Bearer | No |
both | Cookie and JSON body | Bearer preferred, cookie fallback | Yes |
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 tokenprocess_input.auth—{ user_id, email, role, webapp_id, type }when the token is a validwebapp_accessJWT
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: trueif you also used cookies. - Cookie / both: return
clear_auth_cookie: truefrom the logout process. The runtime clearstf_sessionand revokes the presented JWTjtiwhen present.
Passwords and tokens in logs
| Data | Store in the database | Logs / 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 / Authorization | Do not store full JWTs in WebAppExecutionLogs or process history. App-owned session tables may store hashes or jti. | Masked only (prefix/suffix). |
| Email / username | Allowed | Allowed |
Do not tenantDb.insert raw passwords. Do not console.log login payloads. Compare hashes; never log process_input.password.