Authentication
Aleta authenticates API traffic with signed JWT assertions, not shared secrets. You generate an RSA key pair, register the public half with the platform, and sign a short-lived JWT every time you need a new access token. The private key never leaves your infrastructure; the platform never holds a secret that could be stolen off it.
Why JWT assertions?
Client secrets in wealth infrastructure are a liability. Once issued, they're stored somewhere, and "somewhere" is eventually a log file, an environment dump, or a misconfigured secrets manager. Signed assertions avoid that entire class of problem: the credential that can actually be used against the API is a private key you control, and the thing we see is a public key we can verify against. Rotating is a local operation on your side; you don't need to coordinate a handoff with us to replace it.
One-time setup
1. Generate a key pair
An RSA-2048 key is fine for most deployments; RSA-4096 is accepted if your security posture requires it. OpenSSL, Node's crypto.generateKeyPair, and Python's cryptography all work.
With OpenSSL
openssl genpkey -algorithm RSA -out aleta-private.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -in aleta-private.pem -pubout -out aleta-public.pem
Store the private key the way you'd store any production secret — a secrets manager, an HSM, encrypted at rest. Our SDKs read the PEM by path or env var; don't commit it to the repository.
2. Register the public key
Upload the public half to your workspace. Each key gets a stable key_id that becomes the kid claim on every assertion you sign with it. You can have multiple active keys at once, which is how graceful rotation works: the new key is added, production traffic shifts to signing with it, the old one is removed after the last caller has rolled.
Register a public key
curl -X POST https://api.aleta.io/v2/auth/keys \
-H "Authorization: Bearer {admin_token}" \
-H "Content-Type: application/json" \
-d @- <<'JSON'
{
"label": "prod-backend",
"public_key_pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
}
JSON
The response carries the key_id — that's the value you'll plug into every assertion's kid header.
Per-request flow
3. Sign an assertion JWT
Every time you want an access token, construct a JWT with the claims below and sign it with your private key. Standard RS256 is expected.
- Name
iss- Type
- string
- Description
Your workspace identifier — shown in the dashboard next to the key you registered.
- Name
sub- Type
- string
- Description
Same value as
issfor server-to-server integrations. For user-delegated flows, the user's ID.
- Name
aud- Type
- string
- Description
Always
https://api.aleta.io.
- Name
exp- Type
- unix timestamp
- Description
Assertion expiry. Keep this short — 60 seconds from now is plenty, since assertions are one-shot.
- Name
iat- Type
- unix timestamp
- Description
Issued-at. Same clock the server uses for
expvalidation; don't skew more than 30 seconds.
- Name
jti- Type
- string
- Description
Unique per-assertion identifier (UUID is fine). Prevents replay within the
expwindow.
The JWT header needs alg: RS256 and kid: {your_key_id}.
Signing in Node.js
import { readFileSync } from "fs";
import { SignJWT, importPKCS8 } from "jose";
const privateKey = await importPKCS8(
readFileSync(process.env.ALETA_PRIVATE_KEY_PATH!, "utf8"),
"RS256",
);
const assertion = await new SignJWT({})
.setProtectedHeader({ alg: "RS256", kid: process.env.ALETA_KEY_ID! })
.setIssuer(process.env.ALETA_WORKSPACE_ID!)
.setSubject(process.env.ALETA_WORKSPACE_ID!)
.setAudience("https://api.aleta.io")
.setIssuedAt()
.setExpirationTime("60s")
.setJti(crypto.randomUUID())
.sign(privateKey);
4. Exchange for an access token
POST the assertion to the token endpoint. The response is a short-lived bearer token (1 hour by default) that you attach to every subsequent request.
Token exchange
curl -X POST https://api.aleta.io/v2/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
--data-urlencode "assertion=${ASSERTION_JWT}"
Response
{
"access_token": "aleta_live_9b1c…",
"token_type": "Bearer",
"expires_in": 3600
}
Cache the access token in memory until it's about to expire, then re-sign and re-exchange. Don't pre-mint assertions — one per token request.
5. Call the API
Authorised request
curl https://api.aleta.io/v2/clients \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
All of the above is handled for you by the Aleta SDKs — supply the private key path at construction time and they take care of signing, caching, and refreshing on every call.
Rotation
- Register a new public key — you'll get a second
key_id. - Deploy the new private key +
key_idto production. - Watch metrics for traffic on the old key draining to zero.
- Delete the old public key from Aleta. Old assertions signed against it stop verifying immediately.
Related
- Idempotency — always paired with tokens in production.
- API reference — starts here once you're authenticated.