Skip to content

Access tokens

Better Auth sessions, OpenTofu customer and managed-run credentials, and audience-bound MCP OAuth tokens.

Updated View as Markdown

StackShip uses separate credentials for separate protocol surfaces. A token for one surface is not a general-purpose API key.

Credential Accepted by Purpose
Better Auth session Dashboard and native /v1 API Human product access in an active organization
Service-account bearer token OpenTofu /api/v2 compatibility surface Workspace- and project-bounded automation
OpenTofu OAuth opaque token OpenTofu /api/v2 compatibility surface User access evaluated against current authorized memberships
Managed-run opaque credential OpenTofu /api/v2 compatibility surface from the managed sandbox One active plan or apply attempt
MCP OAuth JWT https://mcp.stackship.run/mcp and, for the confined MCP automation surface, native /v1 Organization-bound reads with mcp:tools; mutations also need mcp:write
Internal capability A specific internal runner or broker route One bounded execution action

The separate StackShip CLI is a roadmap item and is not available in private beta.

OpenTofu service accounts

An authenticated principal with credential:write can open a workspace, choose CLI access, create a service account, and issue one token bounded to that workspace and its project. The dashboard offers only explicit purpose presets:

Purpose Exact permissions
State migration workspace:read, state:outputs:read, state:read, state:recover, state:write, state:lock
Remote plan and apply workspace:read, state:read, run:create, run:read, run:approve, policy:read

Expiry is selected explicitly from 1, 7, 30, 90, or 365 days. The API rejects an empty permission set, an unknown permission, a lifetime beyond 365 days, and permission escalation beyond the creating principal.

The token secret is returned only by the issuance response. Store it in the OpenTofu credential location or an external secret manager. StackShip stores a digest, not the bearer plaintext. An exact issuance retry by the same authenticated principal with the same request and Idempotency-Key re-derives the same token to recover a lost response; the metadata-only idempotency record does not contain the token. Reusing that key with changed input conflicts. The dashboard therefore reuses the exact request and key when a failed issuance is retried unchanged, but creates a new bounded request and key when the operator changes the purpose or expiry. It removes the plaintext from its rendered state after I saved this token is acknowledged. It does not provide token inventory because the v1 API has no list operation. Revocation invalidates subsequent use through the API.

For non-interactive OpenTofu, provide the one-time value through the hostname-derived environment variable:

export TF_TOKEN_stackship_run='<service-account-token>'
tofu init
tofu plan

OpenTofu uses that credential with the native cloud protocol under /api/v2. It is not accepted as a Better Auth dashboard session and it is not an MCP credential.

Managed-run credentials

StackShip never forwards a customer’s OAuth or service-account credential into the sandbox. For a managed plan or apply, the runner instead receives a separate opaque rat_ credential through the same hostname-derived TF_TOKEN_stackship_run environment variable. The API derives this credential with the dedicated RUNNER_RUN_TOKEN_HMAC_KEY; it is distinct from customer service-account tokens and from runner bootstrap, session, and artifact capabilities.

The API accepts the credential only for its exact organization, project, workspace, run, attempt, phase, sandbox, and current workspace fence. Its state authority is phase-specific:

Attempt Read state Lock state Publish state
Managed plan Current state For the OpenTofu command Never
Speculative plan Pinned state version only Never Never
Managed apply Current predecessor For the OpenTofu command Only with the active reservation, owned lock, expected predecessor, and current fence

A terminal, revoked, expired, replaced, or stale attempt fails closed. The credential never grants run:create or state:recover, is not a customer credential, and is rejected by the dashboard, native /v1, and MCP surfaces.

OpenTofu OAuth login

Interactive tofu login stackship.run uses the fixed public tofu-cli client, authorization code flow, and PKCE. The resulting access token is an opaque bearer credential whose digest is stored by Better Auth. It expires after 90 days and has only tfe:access. The client cannot request offline_access, so no refresh token is issued.

This user token is not bound to the organization active during consent. For each cloud-protocol request, StackShip resolves the requested organization and requires the user to have a current authorized membership there. Removing a membership therefore prevents access to that organization even while the opaque token has time remaining.

The OAuth revocation endpoint deletes the opaque access-token record and makes subsequent requests fail. Removing the credential from OpenTofu’s local configuration only removes that local copy; it does not itself promise server-side revocation.

MCP OAuth

The MCP server publishes RFC 9728 protected-resource metadata. Clients obtain an access token from the API’s OAuth 2.1 authorization server using PKCE. The access token is a signed JWT bound to the organization selected during authorization and expires after one hour. Validation requires:

  • a valid issuer and signature;
  • an unexpired token;
  • mcp:tools;
  • the configured MCP resource as the token audience; and
  • one exact organization claim.

Mutating MCP tools additionally require the mcp:write scope. Missing or mismatched claims fail closed with a bearer challenge. Browser cookies and OpenTofu service-account tokens are not accepted at the MCP endpoint.

The same JWT authenticates the confined MCP automation surface on native /v1, where the API resolves it against its hashed token record and applies live-membership tenant derivation; see the v1 authentication rules.

Clients that request offline_access receive a hashed, opaque refresh token with a 30-day expiry. Each successful refresh rotates that token and revokes the previous value. Revoking a refresh token prevents future refreshes. Revoking an already issued MCP JWT cannot invalidate the MCP Worker’s own local check immediately because the Worker verifies JWTs without a revocation list; tool execution still stops immediately, because the API resolves every tool call against the hashed token record and refuses revoked or deleted grants. Changing organizations requires a new authorization.

OAuth consent and token protocol records are not product audit events. StackShip does not currently record consent decisions, issuance, refresh, or revocation in the audit log, and no organization-settings OAuth revocation UI is available.

Handling rules

  • Never put a bearer credential in a URL or query parameter.
  • Never log tokens, authorization headers, session cookies, managed-run credentials, or internal capabilities.
  • Give a service-account token only the permissions and lifetime it needs.
  • Revoke a token when its consumer is retired or its custody is uncertain.
  • Treat a downloaded state file as a credential-bearing artifact even though the download itself used authenticated streaming.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close