Security Best Practices
Security best practices to read before you ship an integration.
Read this before you ship. The API's security model rests on you protecting one thing —
your RSA private key — and handing untrusted clients only short-lived, scoped
access tokens.
The one rule that matters most
Never let your RSA private key leave your backend.
Anyone holding the private key can mint valid tokens for your entire organization. That
means:
- ❌ Never embed it in browser JavaScript, mobile apps, desktop apps, or any client you
distribute. - ❌ Never commit it to a git repository, paste it into an issue, or log it.
- ❌ Never send it to Verbatim — only the public key is ever uploaded.
- ✅ Store it with restrictive permissions (
chmod 600) or, better, in a secrets manager
(Vault, AWS/GCP Secrets Manager, etc.). - ✅ Give it the smallest possible blast radius: separate keys per environment
(staging vs production) and per service.
If a browser or mobile client needs API access, mint an
access token for it instead.
Transport
- Always use HTTPS. Use the
https://…verbatim-ai.combase URLs. Never send a token
over plain HTTP — it can be captured in transit. - Don't put tokens in URLs where you can avoid it (query strings land in server logs, proxy
logs and browser history). Prefer theAuthorization/X-Access-Tokenheaders.
Least privilege
- Scope every access token to exactly what the client needs and nothing more
(see scopes). Scope is mandatory for access
tokens for this reason. - For RSA JWTs, remember an empty scope means unrestricted. Reserve unscoped JWTs for
trusted server-to-server automation; add ascopeclaim when a narrower token will do. - Prefer separate keys/tokens per integration so you can revoke one without disrupting the
others.
Keep token lifetimes short
- Access tokens default to 1 hour (
ttl= 3600s). Keep them short; mint fresh ones as
needed rather than issuing long-lived tokens. - Sign RSA JWTs with a short
exptoo (minutes for interactive flows). A leaked token stops
working when it expires. - Provide a lightweight "get me a fresh token" endpoint on your backend for frontends, and
refresh before expiry.
Rotation & revocation
- Rotate keys periodically and immediately on any suspected compromise. Because published
PEM content is write-once, rotation is: generate a new pair → publish it → point your
signer at the new key id → delete the old key. See
key lifecycle. - Deactivate a key to instantly (and reversibly) stop honoring tokens signed with it;
delete to retire it permanently. - Revoke access tokens on sign-out or suspected leak with
DELETE /v1/auth/access-token/{token}. - Have a written plan for "a key/token leaked": deactivate/revoke first, rotate, then
investigate.
Handling tokens in the browser
- Treat any access token as a bearer secret for its (short) lifetime.
- Avoid persisting tokens in
localStoragewhen you can keep them in memory; if you must
persist, prefer short TTLs and clear on logout. - Scope browser tokens tightly (usually read + query only). A leaked, tightly-scoped,
short-lived token has limited value. - Never expose the backend endpoint that mints tokens without its own authentication —
otherwise anyone can request tokens for your organization.
CORS & origins
The API responds with permissive CORS so browser clients can call it directly. CORS is not
an authorization boundary — it controls which sites the browser lets read responses, not
who may call the API. Your real access control is the token and its scope. Don't rely on
origin checks for security; rely on short-lived, scoped tokens.
Checklist
- Private key stored server-side only,
chmod 600or in a secrets manager. - Separate keys per environment (staging / production).
- Browser/mobile clients use scoped access tokens, never the private key.
- Access token scopes are minimal.
- Token lifetimes are short; frontends refresh before expiry.
- HTTPS everywhere; tokens in headers, not URLs.
- Documented rotation & revocation procedure.
- Token-minting endpoint on your backend is itself authenticated.
See also: Authentication · RSA keys ·
Access keys
Updated about 1 hour ago