.agents/features/app-connections.md
App Connections store encrypted authentication credentials (OAuth2 tokens, API keys, basic auth, custom piece-defined fields, or OIDC props) that flow steps use to call external services. They support automatic OAuth2 token refresh with distributed locking, a dual-scope model (project-level or platform-wide), and a "replace" operation that atomically rewires all flow references from one connection to another. The module handles all OAuth2 variants: user-supplied credentials, platform-managed OAuth apps, and Activepieces-hosted cloud OAuth. Users can optionally select a subset of a piece's declared OAuth2 scopes when creating a connection. OIDC connections enable pieces to obtain short-lived credentials from cloud providers (e.g. AWS via IRSA/Web Identity) using the Activepieces platform as an OIDC identity provider.
packages/server/api/src/app/app-connection/ — backend module (controller, service, entity)packages/core/shared/src/lib/automation/app-connection/app-connection.ts — core types, enums, and value union typespackages/core/shared/src/lib/automation/app-connection/dto/upsert-app-connection-request.ts — upsert DTOpackages/core/shared/src/lib/automation/app-connection/dto/read-app-connection-request.ts — list query DTOpackages/core/shared/src/lib/automation/app-connection/oauth2-authorization-method.ts — OAuth2 authorization method enumpackages/web/src/features/connections/api/app-connections.ts — frontend API clientpackages/web/src/features/connections/api/global-connections.ts — global (platform-scope) connections API clientpackages/web/src/features/connections/hooks/app-connections-hooks.ts — TanStack Query hooks (appConnectionsQueries, appConnectionsMutations)packages/web/src/features/connections/hooks/global-connections-hooks.ts — global connections hookspackages/web/src/features/connections/utils/oauth2-utils.ts — OAuth2 redirect URL helperspackages/web/src/features/connections/utils/utils.ts — name-uniqueness check helperspackages/web/src/app/routes/connections/index.tsx — project connections list pagepackages/web/src/app/routes/platform/setup/connections/index.tsx — platform-wide global connections pagepackages/web/src/app/connections/new-connection-dialog.tsx — new connection dialog wrapperpackages/web/src/app/connections/create-edit-connection-dialog.tsx — create/edit connection form dialogpackages/web/src/features/connections/components/edit-global-connection-dialog.tsx — edit global connection dialogpackages/web/src/features/connections/components/rename-connection-dialog.tsx — rename connection dialogpackages/web/src/app/connections/oidc-connection-settings.tsx — OIDC connection form componentpackages/server/api/src/app/core/security/oidc/oidc-key-manager.ts — RSA key lifecycle: mutex-protected caching, auto-generation persisted (encrypted) to the shared flag table, RFC 7638 kid fingerprintpackages/server/api/src/app/core/security/oidc/oidc.module.ts — module wrapper that registers the OIDC token controller under /v1/workerpackages/server/api/src/app/core/security/oidc/oidc-token.controller.ts — engine-only endpoint that issues RS256 JWTs (POST /api/v1/worker/oidc-token)packages/server/api/src/app/core/security/oidc/oidc-discovery.controller.ts — public OIDC discovery endpoints (GET /.well-known/openid-configuration, GET /.well-known/jwks.json)globalConnectionsEnabled plan flag.secrets.activepieces.com for token exchange.Canonical term definitions live in the bounded-context glossaries — see CONTEXT-MAP.md.
PROJECT (restricted to projects in projectIds[]) or PLATFORM (available to all projects).OAUTH2, CLOUD_OAUTH2, PLATFORM_OAUTH2, SECRET_TEXT, BASIC_AUTH, CUSTOM_AUTH, NO_AUTH, OIDC.{ type: OIDC, props: T } — piece-defined OIDC props (role ARN, audience, etc.) stored encrypted; the actual token is fetched at runtime from the POST /api/v1/worker/oidc-token endpoint./.well-known/openid-configuration and /.well-known/jwks.json so cloud providers (e.g. AWS STS) can verify issued tokens.projectIds for every new project.PLATFORM-scope connection managed from the platform admin UI, shared across all (or selected) projects.AppConnection: id, displayName, externalId, type (AppConnectionType), status (ACTIVE/EXPIRED/ERROR), value (EncryptedObject), platformId, pieceName, pieceVersion, ownerId (nullable FK), projectIds[] (string array — multi-project), scope (PROJECT/PLATFORM), metadata (JSONB), preSelectForNewProjects (boolean).
| Type | Value Fields | Refresh |
|---|---|---|
| OAUTH2 | access_token, refresh_token, client_id, client_secret, token_url, expires_in, claimed_at | Auto-refresh |
| CLOUD_OAUTH2 | Same but tokens exchanged via secrets.activepieces.com | Auto-refresh via cloud |
| PLATFORM_OAUTH2 | Same but uses platform-managed OAuth app credentials | Auto-refresh |
| SECRET_TEXT | token | None |
| BASIC_AUTH | username, password | None |
| CUSTOM_AUTH | piece-defined custom fields + optional access_token, token_refresh_at | Optional — piece opts in via refresh callback; server caches token with 15-min early-refresh buffer (clamped to half the token lifetime) |
| NO_AUTH | (empty) | None |
| OIDC | piece-defined props (e.g. role ARN, audience) — token fetched at runtime | None (short-lived JWT issued per-request) |
Activepieces acts as an OIDC identity provider to enable pieces to assume cloud roles without long-lived credentials (e.g. AWS IRSA / Web Identity Federation).
Runtime flow:
POST /api/v1/worker/oidc-token with { audience, expiresInSeconds? } (engine-only endpoint, guarded by securityAccess.engine())sub: platform:{platformId}:project:{projectId}, aud set to the requested audience (e.g. sts.amazonaws.com), TTL defaults to 1 hour (capped at 1 hour)AssumeRoleWithWebIdentity) to get temporary credentialsCaller contract (pieces calling from run() via context.server):
audience (non-empty after trim) is mandatory; expiresInSeconds is optional (integer, 60–3600, default 3600). Requests without a body get 400.const response = await fetch(`${server.apiUrl}v1/worker/oidc-token`, {
method: 'POST',
headers: {
Authorization: `Bearer ${server.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ audience: 'sts.amazonaws.com' }),
})
const { token } = await response.json()
Discovery endpoints (public, CORS-open, available when system.isApp() is true):
GET /.well-known/openid-configuration — issuer metadata pointing to the JWKS URIGET /.well-known/jwks.json — RSA public key set; kid is an RFC 7638 SHA-256 thumbprintKey management (oidcKeyManager):
flag table (encrypted with the platform encryption key)INSERT ... ON CONFLICT DO NOTHING + re-read, giving first-writer-wins convergence across all nodes (single- and multi-node alike — no env var or manual provisioning required)privateKeyMutex and publicKeyMutex guard caching independently to avoid deadlocksprojectIds[] array. Query with ArrayContains([projectId]).Automatic on connection retrieval:
lockAndRefreshConnection() checks if OAuth token expired (15-min early refresh threshold)key = ${platformId}_${externalId}, 60s timeout) — keyed on the connection's project-invariant identity so projects sharing a connection serialize on one lockrefresh() method (different per type: cloud/platform/credentials)Opt-in on connection retrieval — piece authors define a refresh callback in CustomAuthProperty:
PieceAuth.CustomAuth({
props: { ... },
refresh: {
generate: async ({ auth }) => {
const res = await httpClient.sendRequest({ ... })
return { access_token: res.body.token, expires_in: 3300 }
},
defaultExpiresIn: 3300, // seconds; used when generate() omits expires_in
},
})
Runtime flow:
needRefresh() checks connection.value.access_token:
now >= token_refresh_at (the precomputed refresh instant)pieceRefreshSupportCache (in-process LRU, 500 entries, 5-min TTL keyed by pieceName@pieceVersion); on cache miss, load piece metadata and check for refresh callback; result cached for future executionskey = ${platformId}_${externalId}, 60s)EXECUTE_TOKEN_REFRESH worker job (user-interaction queue, same pattern as EXECUTE_VALIDATION)refresh.generate() callback, returns { access_token, expires_in? }access_token and token_refresh_at stored encrypted in CustomAuthConnectionValue, status=ACTIVE. token_refresh_at = now + expiresIn - min(15 min, expiresIn / 2) so the 15-min early-refresh buffer never exceeds half the token's lifetime (a short-lived token would otherwise be stale the instant it is minted); expiresIn <= 0 means "never expires" → token_refresh_at is left unsetCustomAuthRefreshError → sets status=ERRORrefresh callback (returns skipped: true): clears stale access_token/token_refresh_at and sets cache to false so no further jobs fireInside piece actions/triggers, context.auth.access_token holds the cached token alongside the raw props.
POST /v1/app-connections — create/upsert connection (validates via worker EXECUTE_VALIDATION job)POST /v1/app-connections/:id — update displayName, metadata, preSelectForNewProjectsGET /v1/app-connections — list with filters (pieceName, displayName ILIKE, status, scope, externalIds)GET /v1/app-connections/owners — list connection owners (platform admins + project members)POST /v1/app-connections/replace — replace source connection with target across all flowsDELETE /v1/app-connections/:id — hard deletePOST /v1/app-connections/oauth2/authorization-url — generate OAuth redirect URL from piece metadata; accepts optional scopes array to restrict the authorization to a user-selected subset of the piece's declared scopes (all piece scopes used when omitted)replace() atomically updates all flow references (published + draft versions)All connection values encrypted with encryptUtils.encryptObject() (AES-256) before storage, decrypted on retrieval.
The project connections page (/connections) shows a data table with status badges, piece icons, scope indicators, and bulk-delete support. The new-connection-dialog opens create-edit-connection-dialog which renders piece-auth-specific form fields. The "Replace" action opens replace-connections-dialog which calls the replace endpoint. The platform admin global connections page (/platform/setup/connections) uses global-connections-hooks and surfaces edit-global-connection-dialog for managing platform-scope connections and their projectIds assignment. The builder uses connection-select inside step settings to pick, create, or reconnect connections inline; active connections show a "Connected" status with a compact reconnect icon, while connections needing re-authentication keep a "Reconnect" action.