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:

GrantToken flavorAudienceEndpoints authorised
passwordHUMANankasecure-admin/api/v3/admin/* — provisioning, rotation, revocation
client_credentialsAPPLICATIONankasecure-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)derivedderived (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 typeWho uses itCredentials required
client_credentialsWorkloads / apps (server-to-server)client_id + client_secret (UUIDs)
passwordHuman admins using CLI / portalusername (email) + password + tenant_id
refresh_tokenAny client renewing a sessionrefresh_token
urn:ietf:params:oauth:grant-type:token-exchangeImpersonation / 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)

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)

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/keys

Java (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)

PermissionGrants
admin.keys.readList keys, read metadata (no cryptographic material)
admin.keys.rotateAdvance a key to a new material version
admin.keys.revokeRevoke a key (blocks all future ops)
admin.keys.suspendTemporarily suspend a key
admin.keys.archiveMove a revoked key to archived state
admin.keys.orchestrateBulk key operations (rotate/revoke/suspend at scale)
admin.policy.effective.readRead the effective policy resolved for a tenant
admin.policy.templates.readRead policy templates
admin.cryptographicexchange.*Manage cryptographic exchanges (control-plane orchestration)
admin.humanuserFull 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) for client_credentials. Shorter (~15 min) for high-risk grants like token exchange.
  • Refresh token: issued for password and refresh_token grants; not issued for client_credentials (re-call /token instead).
  • Recommended pattern: cache the token in memory and refresh it either (a) 60 seconds before expires_in, or (b) when a request returns 401.

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 a tenantId in the path or body — it is derived from the token.
  • Admin endpoints (/api/v3/admin/tenants/{tenantId}/...) take the target tenantId in 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

HTTPOAuth error codeWhat went wrongFix
400invalid_requestMissing required field (e.g. no grant_type), or malformed bodyRecheck the form-urlencoded body — no JSON
400unsupported_grant_typeYou sent JSON instead of application/x-www-form-urlencodedFix the Content-Type header and re-encode the body
400invalid_grantclient_credentials: bad client_secret; password: bad passwordRecheck credentials — remember they are environment-specific
401invalid_clientclient_id unknown or workload disabledConfirm the client_id with the admin who provisioned it
401(no error field, token error)Bearer token past its expires_in, or signature invalidRenew via /token — cache the new one
403(no error field, permission)Token is valid but the role lacks the required permissionAsk admin to grant the missing permission; reissue the token
429too_many_requestsRate 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_secret rotation: ask the admin to generate a new secret for your client_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 endpointPOST /api/v3/auth/token
Switch to production credentialsEnvironments & Credentials
Try a first encrypt in 5 minutesQuickstart
Revoke a token programmaticallyPOST /auth/token/revoke
Inspect a tokenPOST /auth/token/introspect
See the full permissions catalogueGET /api/v3/admin/platform/scopes (look in the Admin section)

Did this page help you?