Authentication
How people and machines prove who they are.
View as MarkdownEvery principal, human or machine, authenticates against WorkOS. We validate what it issues and never mint our own credentials.
That gives one identity system rather than two, and it means a revocation in one place is a revocation everywhere.
The three principals
| Principal | Credential | Presented as |
|---|---|---|
| Human in the dashboard | AuthKit session, JWT access token | Authorization: Bearer <jwt> |
| Human in the CLI | Same, obtained by device code | Authorization: Bearer <jwt> |
| Machine: API key or worker | Org-scoped API key, opaque | Authorization: Bearer <key> |
All three resolve to the same internal principal shape, so a shared endpoint such as presign, confirm or download runs one handler regardless of who called it. There is no "machine path" with different rules.
Humans
Dashboard
Standard AuthKit: OAuth authorization code with PKCE, and a sealed session cookie. The dashboard forwards the current access token to our API as a bearer token.
CLI
sctl login uses the OAuth device code flow, which is the right choice for a terminal:
no local callback server, no browser embedded in a CLI, and it works over SSH.
sctl loginIt prints a code, you approve it in a browser on any device, and the session lands in a local config file. Tokens are refreshed automatically.
sctl workspace list
sctl workspace switch <name>A session is scoped to exactly one workspace. Switching workspaces gets a new access token carrying that workspace's permissions. There is no token that spans two, which is what makes the tenancy boundary a property of the credential rather than of a filter.
Where the CLI stores its session:
| Platform | Path |
|---|---|
| Linux | ~/.config/sctl |
| macOS | ~/.config/sctl |
| Windows | %APPDATA%\sctl |
Treat that file as a credential. There is no sctl logout: delete the file to end the
session on that machine, which is the first thing to do on a shared or borrowed one.
Token validation
| Property | Value |
|---|---|
| Algorithm | RS256, verified against the AuthKit JWKS |
| Cached | JWKS cached for a few minutes |
| Checked | Issuer, and the set of accepted client IDs |
| Read for authorization | The permissions claim, and nothing else |
The token also carries our own workspace and user IDs, mirrored into WorkOS as external IDs, so the backend reads its own identifiers straight from a validated token with no per-request database lookup.
role is carried in the token and is display only. The backend authorizes on
permissions and never reads role. See Permissions.
Machines
API keys and worker keys are the same kind of credential: an organization-scoped WorkOS API key, opaque rather than a JWT, sent as a bearer token.
| Property | Behaviour |
|---|---|
| Validation | Checked against WorkOS on use, and cached for about 60 seconds |
| Revocation | Propagates within the cache TTL, or immediately when the revocation webhook busts it |
| Scope | Bound to one organization, which is one workspace |
| Storage | We store the key's identifier. The secret itself is shown once and never persisted |
Revocation is not instantaneous. A revoked key can continue to work for up to about a minute while a cached validation is still live, and on the orchestration side the cache cannot be busted early, so there it is always TTL-bound. Plan a revocation as "effective within a minute", not "effective now", and rotate source credentials too if a key leaked.
Worker keys are also the orchestration credential
A worker key authenticates twice with the same secret: to our API for presign and confirm, and to the orchestration hub to poll for work. It carries exactly two permissions, and the hub requires one of them to admit a worker at all.
See Permissions and Registration.
Workspaces and membership
| Fact | Consequence |
|---|---|
| A workspace is one WorkOS organization, 1:1 and flat | There is no organization above a workspace |
| Identity is global, access is per-workspace | One person, one identity, many possible workspaces |
| Membership and roles live in WorkOS | Not in our database, so there is one place to audit them |
| A worker belongs to one workspace, fixed at provisioning | It cannot be moved |
Creating a workspace is human-only, and it is one of the very few gates that is not a permission check. A brand-new user has no workspace and therefore no workspace-scoped permission to check, so the rule is expressed on the principal's kind instead: a machine credential cannot create a workspace, whatever it holds.
Multi-factor
MFA is configured in WorkOS at the organization level, and it applies to human sign-in. Because WorkOS is the trust anchor, enabling it there covers both the dashboard and the CLI without any change on our side.
Machine keys are not subject to MFA, which is what makes them appropriate for automation and also what makes their scoping matter. See API keys.
Sessions and sign-out
| Action | Effect |
|---|---|
| Deleting the config file | Ends the local session on that machine |
| Signing out of the dashboard | Clears the session cookie |
| Removing a member in WorkOS | Ends their access to that workspace |
| Revoking an API key | Stops it within the validation cache TTL |
Removing a member does not revoke API keys they created. A key belongs to the workspace, not to the person who made it, which is deliberate so automation does not break when someone leaves. It also means offboarding has a second step: review the workspace's keys.