Sign in with a private key
Let a workload sign in with a key pair, and replace a legacy client secret with a private key.
If your job runs somewhere without its own identity tokens, such as an on-premises server, give its workload a key pair. You keep the private key in your secret manager, and Spiral stores only the public key, so nothing that can sign in as your job ever leaves your hands. For each sign-in, the job signs a short-lived assertion with the private key.
If the job runs on GitHub Actions, GCP, Kubernetes, AWS, or Modal, use a platform identity instead: there is no key to store or rotate.
Prerequisites
- You created a workload and granted it a Project role.
- You have the Project
adminrole to manage the workload's keys. A key lets its holder act as the workload, with all of the workload's grants. - You have a secret manager for the job's environment.
Add a key pair
Create the key pair on a computer that you control:
spiral workloads keys add work_01GHIThe command creates a P-256 key pair and registers the public key with the
workload. It writes the private key, as a JWK, to work_01GHI.jwk. Only your
user can read the file. Move the private key to your secret manager, then
delete the file. To write the private key to standard output instead, add
--output -.
Add --kid deploy-1 to give the key a descriptive ID. Studio lists this
value in the workload's Credentials section. Keys have a key ID (kid)
rather than a separate display name.
Set the workload ID and the private key in the job's environment:
export SPIRAL_CLIENT_ID=work_01GHI
export SPIRAL_PRIVATE_KEY="$(cat work_01GHI.jwk)"In Studio, open the workload and select Register public key, then open Generate key pair and select Generate key pair & download private key. Key generation runs entirely in your browser. Studio immediately starts a download of the private JWK and fills the public key field. Store the downloaded private key securely, then select Register public key to submit only its public half. Studio does not save the private key in browser storage.
When you set up your own Workers from a Project's Tasks page in Studio, Studio also creates the workload's key pair in your browser and registers only the public key.
To register an existing SSH public key, open Import public key and paste
its .pub contents into Public key, drop the file there, or select
Choose file…. Studio accepts RSA, ECDSA (P-256, P-384, P-521), and
Ed25519 SSH keys, as well as public JWK files, up to 16 KiB. It converts
SSH keys to JWKs in your browser and previews the public key before you
select Register public key. SSH certificates and hardware security-key
formats are unsupported.
SSH keys default to their SHA-256 fingerprint as the key ID. Enter a
Key ID to choose a descriptive ID instead. A JWK keeps its existing
kid, or takes the ID you enter if it has none. Use that same ID in the
JWT header when signing assertions. SDK sign-in requires a private JWK
with the matching kid; the SSH private-key file stays on your computer.
Generate a key pair with ssh-keygen
Generate a P-256 key pair in your terminal. The private key uses PEM format without a passphrase so the CLI can read it:
ssh-keygen -t ecdsa -b 256 -m PEM -N '' -f workload-private.pemDrop workload-private.pem.pub into Studio's public-key form or paste its
contents, then select Register public key. Keep workload-private.pem
private.
Alternatively, register the key with the CLI, which converts it locally and sends only the public half. The output file holds the private JWK that the SDK accepts:
spiral workloads keys add work_01GHI --key workload-private.pem --output workload-private.jwkMove the private files into the job's secret manager.
Register a key that you already have
Pass the key file with --key. The file can hold a public or a private RSA,
EC, or Ed25519 key, as a JWK or in PEM. Spiral receives only the public key.
SPIRAL_PRIVATE_KEY takes a JWK that names the kid of the key. For a
private key in PEM, add --output to write that JWK:
spiral workloads keys add work_01GHI --key workload.pem --output workload.jwkOver HTTP, register a public key as a JWK that names its kid. In this
example, public-key.jwk holds that JWK:
curl -X POST https://api.spiraldb.dev/api/workloads/work_01GHI/keys \
-H "Authorization: Bearer $SPIRAL_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"jwk\": $(cat public-key.jwk)}"Verify and rotate keys
List the workload's keys:
spiral workloads keys ls work_01GHIA workload can have more than one key. To rotate a key, add the new key,
deploy the new private key, and then remove the old key with
spiral workloads keys rm. Assertions signed with a removed key no longer
sign the workload in.
Exchange a key assertion over HTTP
A client without the SDK signs a client assertion with the private key
(private_key_jwt client authentication,
RFC 7523, section 3). The workload
ID is also the workload's OAuth client ID. Set these claims:
| Claim | Value |
|---|---|
iss and sub | The workload ID. |
aud | https://auth.spiraldb.com/oauth/token |
jti | A unique value for each assertion. |
iat and exp | The issue time, and an expiry a few minutes later. |
Put the kid of the key in the JWT header. Then send the assertion with the
client_credentials grant:
curl -X POST https://auth.spiraldb.com/oauth/token \
-d grant_type=client_credentials \
-d client_id=work_01GHI \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
-d client_assertion="$CLIENT_ASSERTION"The server metadata at
https://auth.spiraldb.com/.well-known/oauth-authorization-server lists the
accepted signing algorithms.
Replace a client secret
Spiral no longer issues client secrets. An existing secret, set as
SPIRAL_CLIENT_ID=spw_... and SPIRAL_CLIENT_SECRET, still works. Unlike a
private key, though, a secret is something both you and Spiral hold a copy
of, and it does not expire. Replace it with a private key:
-
Find the workload that owns the secret. This command lists the Project's workloads:
spiral workloads ls demo -
Add a key pair to the workload:
spiral workloads keys add work_01GHI -
In the environment of the workload, replace the client secret with the private key:
export SPIRAL_CLIENT_ID=work_01GHI export SPIRAL_PRIVATE_KEY="$(cat work_01GHI.jwk)" unset SPIRAL_CLIENT_SECRET -
Run the workload, and make sure that it signs in.
-
Revoke the client secret:
spiral workloads revoke-secret spw_01MNO
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
the private key JWK must name the kid it was registered with | SPIRAL_PRIVATE_KEY has no kid. | Write the JWK again with spiral workloads keys add --output. |
The token endpoint returns invalid_client | The kid in the assertion header names no key of the workload, or the key was removed. | Compare the kid with spiral workloads keys ls. |
Where next: the Workloads API lists the key operations.