Authentication
OAuth 2.0 (RFC 6749) — client_credentials for workloads, password for admin users, token exchange for delegation. One Bearer JWT for all 5 APIs.
Authentication
Every request to ANKASecure requires a Bearer JWT in the Authorization header. Tokens are issued by the Auth API following OAuth 2.0 (RFC 6749) and, for advanced scenarios, RFC 8693 Token Exchange and RFC 7009 Revocation.
Two audiences, two token flavors — the audience of the JWT determines which endpoints it can call:
| Grant | Token flavor | Audience | Endpoints authorised |
|---|---|---|---|
password | HUMAN | ankasecure-admin | /api/v3/admin/* — provisioning, rotation, revocation |
client_credentials | APPLICATION | ankasecure-core | /api/v3/crypto/*, /pqc/*, /audit/* — runtime |
refresh_token | (same as parent grant) | (same as parent) | (same as parent) — renew without re-auth |
token-exchange (RFC 8693) | derived | derived (per resource) | Impersonation / delegation to another tenant |
A password token cannot call runtime crypto endpoints, and a client_credentials token cannot call admin endpoints. Real integrations hold both: an admin identity for provisioning, and one or more application identities for runtime operations.
Grant types at a glance
| Grant type | Who uses it | Credentials required |
|---|---|---|
client_credentials | Workloads / apps (server-to-server) | client_id + client_secret (UUIDs) |
password | Human admins using CLI / portal | username (email) + password + tenant_id |
refresh_token | Any client renewing a session | refresh_token |
urn:ietf:params:oauth:grant-type:token-exchange | Impersonation / delegation (RFC 8693) | subject_token + resource |
Every request is application/x-www-form-urlencoded, not JSON. This is the RFC 6749 wire format — using JSON gets a 400 unsupported_grant_type.
Flow — server workload (client_credentials)
client_credentials)This is the flow your application runtime should use for crypto operations (encrypt / decrypt / sign / verify). It issues an APPLICATION token with audience=ankasecure-core.
┌────────────────────────────────────────────────────────────────┐
│ Admin (out-of-band, once per app × tenant) │
│ - Registers a workload → gets client_id (UUID) + secret │
│ - Assigns a role → the token will inherit those permissions │
│ - Hands credentials to app owner over a secure channel │
└──────────────────────────────┬─────────────────────────────────┘
▼
┌──────────────┐ 1. POST /api/v3/auth/token ┌────────────┐
│ │──────────────────────────────▶│ Auth API │
│ Your app │ form-urlencoded │ │
│ │◀──────────────────────────────│ │
└──────┬───────┘ 2. access_token (JWT) └────────────┘
│ expires_in: 36000 (10h)
│
│ 3. Every subsequent call:
│ Authorization: Bearer <access_token>
▼
┌──────────────────────────────────────────────┐
│ Any endpoint on Core / Admin / PQC / Audit │
│ — the same JWT is valid for all five. │
└──────────────────────────────────────────────┘
Obtain a token — client_credentials
curl -s -X POST https://staging.ankatech.co/api/v3/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=550e8400-e29b-41d4-a716-446655440000" \
--data-urlencode "client_secret=••••••••••••••••••••••••••••••••"Response (application/json):
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 36000,
"passwordChangeEligible": false
}expires_in is in seconds (36 000 s = 10 h). The refresh_token field is absent for the client_credentials grant — renew by re-calling /token.
Flow — human admin (password)
password)This is the flow used by admin humans (CLI, dashboard, provisioning scripts) to manage keys, tenants, policies. It issues a HUMAN token with audience=ankasecure-admin.
Obtain a token — password (human admin)
curl -s -X POST https://staging.ankatech.co/api/v3/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=password" \
--data-urlencode "[email protected]" \
--data-urlencode "password=SecureP@ss123!" \
--data-urlencode "tenant_id=00000000-0000-0000-0000-000000000003" \
--data-urlencode "remember_me=true"Response includes both access_token and refresh_token — use the refresh token to renew without asking the user to log in again.
Using the token
Include on every subsequent request:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Any REST client works. Examples:
curl
curl -H "Authorization: Bearer $TOKEN" \
https://staging.ankatech.co/api/v3/crypto/keysJava (built-in HttpClient)
var request = HttpRequest.newBuilder()
.uri(URI.create("https://staging.ankatech.co/api/v3/crypto/encrypt"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();Python (requests)
r = requests.post(
"https://staging.ankatech.co/api/v3/crypto/encrypt",
headers={"Authorization": f"Bearer {token}"},
json={"kid": "my-key", "data": data_b64},
)Try It (this portal)
Click the 🔒 icon on any endpoint's Try It panel and paste only the token value — ReadMe adds the Bearer prefix itself.
Permissions model
Permissions in ANKASecure are role-based, not scope-parameter based. The token you receive carries the permissions of the identity that requested it — either the workload's assigned role (for client_credentials) or the user's role in the tenant (for password).
You do not request scopes at token time — you receive whatever the role grants. If an endpoint returns 403 Forbidden, the fix is to ask the admin to widen the role, not to re-request the token with a broader scope.
Common permission strings (excerpt from the platform's role catalogue)
| Permission | Grants |
|---|---|
admin.keys.read | List keys, read metadata (no cryptographic material) |
admin.keys.rotate | Advance a key to a new material version |
admin.keys.revoke | Revoke a key (blocks all future ops) |
admin.keys.suspend | Temporarily suspend a key |
admin.keys.archive | Move a revoked key to archived state |
admin.keys.orchestrate | Bulk key operations (rotate/revoke/suspend at scale) |
admin.policy.effective.read | Read the effective policy resolved for a tenant |
admin.policy.templates.read | Read policy templates |
admin.cryptographicexchange.* | Manage cryptographic exchanges (control-plane orchestration) |
admin.humanuser | Full CRUD on human users of a tenant |
admin.platform.* | Platform-wide operations (multi-tenant) |
There are 82 distinct permission strings in the current role catalogue. The full list is exposed via GET /api/v3/admin/platform/scopes — the endpoint for admin.platform.scopes.read.
Runtime app permissions (encrypt / decrypt / sign / verify)
For runtime cryptographic operations, the workload's assigned role determines whether it can call /api/v3/crypto/encrypt, /crypto/decrypt, /crypto/sign, and /crypto/verify. Ask your platform admin for the specific role name that grants those in your tenant — they vary by deployment.
Least-privilege pattern
A workload should receive only the permissions it needs at runtime. A typical app-runtime role includes crypto ops but not admin.keys.rotate or admin.keys.revoke — provisioning belongs to a separate admin identity, not to the app itself.
Token lifetime and renewal
- Default lifetime: 10 hours (
expires_in: 36000) forclient_credentials. Shorter (~15 min) for high-risk grants like token exchange. - Refresh token: issued for
passwordandrefresh_tokengrants; not issued forclient_credentials(re-call/tokeninstead). - Recommended pattern: cache the token in memory and refresh it either (a) 60 seconds before
expires_in, or (b) when a request returns401.
The JWT signing key rotates on the platform side. Do not attempt to validate the token yourself unless you fetch the public JWKs from the well-known endpoint the platform exposes for this purpose.
Tenant scoping
Every token is scoped to a single tenant — the tenantId is embedded as a claim in the JWT.
- Runtime endpoints (
/api/v3/crypto/*) do not take atenantIdin the path or body — it is derived from the token. - Admin endpoints (
/api/v3/admin/tenants/{tenantId}/...) take the targettenantIdin the URL path. The platform verifies your admin token has permission for that specific tenant. - Cross-tenant reads and writes are architecturally impossible for non-platform identities.
Token revocation
Actively invalidate a token before its natural expiry — useful when a device is lost or a credential is rotated. Follows RFC 7009.
curl -X POST https://staging.ankatech.co/api/v3/auth/token/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "token=$TOKEN" \
--data-urlencode "token_type_hint=access_token"Returns 200 OK — RFC-mandated, regardless of whether the token existed. See POST /auth/token/revoke.
Platform admins can force-revoke any token, or list revoked tokens for audit — see Admin: Force revoke and Admin: View revoked tokens.
Token introspection
Inspect a token (yours or someone else's, with the right permission) — follows RFC 7662:
curl -X POST https://staging.ankatech.co/api/v3/auth/token/introspect \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "token=$TOKEN"Returns { "active": true|false, "sub": ..., "exp": ..., "scope": ..., ... }. See POST /auth/token/introspect.
Common errors
| HTTP | OAuth error code | What went wrong | Fix |
|---|---|---|---|
| 400 | invalid_request | Missing required field (e.g. no grant_type), or malformed body | Recheck the form-urlencoded body — no JSON |
| 400 | unsupported_grant_type | You sent JSON instead of application/x-www-form-urlencoded | Fix the Content-Type header and re-encode the body |
| 400 | invalid_grant | client_credentials: bad client_secret; password: bad password | Recheck credentials — remember they are environment-specific |
| 401 | invalid_client | client_id unknown or workload disabled | Confirm the client_id with the admin who provisioned it |
| 401 | (no error field, token error) | Bearer token past its expires_in, or signature invalid | Renew via /token — cache the new one |
| 403 | (no error field, permission) | Token is valid but the role lacks the required permission | Ask admin to grant the missing permission; reissue the token |
| 429 | too_many_requests | Rate limit reached (per-client, per-endpoint) | Back off; response includes Retry-After in seconds |
For non-auth endpoints all errors follow RFC 7807 — response Content-Type is application/problem+json and the body includes a machine-readable type URL. Example:
{
"type": "https://api.ankatech.co/problems/key-not-found",
"title": "Key Not Found",
"status": 404,
"detail": "Key 'my-key' does not exist for this tenant.",
"instance": "/api/v3/crypto/encrypt"
}Rotating credentials
client_secretrotation: ask the admin to generate a new secret for yourclient_id. The admin can keep the old secret valid for a grace period; after that it is revoked automatically.- User password rotation: use
/api/v3/auth/password/forgot(email link) or an admin-triggered reset.
Never commit client_secret values to git. Use your platform's secret manager (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, or your deployment's environment variables).
Where to go next
| I want to… | Read |
|---|---|
| See the actual token endpoint | POST /api/v3/auth/token |
| Switch to production credentials | Environments & Credentials |
| Try a first encrypt in 5 minutes | Quickstart |
| Revoke a token programmatically | POST /auth/token/revoke |
| Inspect a token | POST /auth/token/introspect |
| See the full permissions catalogue | GET /api/v3/admin/platform/scopes (look in the Admin section) |
Updated 10 days ago