Authentication (Keycloak)
What credentials a tenant receives, how to use them, and what they represent. Assetera Identity is a Keycloak realm speaking standard OIDC / OAuth 2.0.
Assetera Identity is a Keycloak realm speaking standard OpenID Connect / OAuth 2.0. Because it is
standard OIDC, any OIDC client library works. As a tenant you are provisioned an OIDC client; every token
that client mints carries your tenant claim, and Assetera's APIs validate it locally against the
realm's public keys (JWKS). No shared secret is held by the API, and there is no per-request round trip back
to Keycloak.
What you are provisioned
When your tenant is onboarded you receive one confidential OIDC client:
| You receive | What it is | What it represents |
|---|---|---|
client_id | Your tenant's client identifier | Your tenant's machine identity |
A client secret or (preferred) a key pair for private_key_jwt | How the client proves itself | Only your backend can mint tokens for your tenant |
| The issuer / realm URL and its discovery document | .../realms/assetera + .well-known/openid-configuration | Where to authorize, get tokens, and fetch keys |
| An audience for the API you call | e.g. marketplace-api | Which API your tokens are valid for |
| Redirect URIs (for user login) | Your BFF callback URLs | Where users return after signing in |
The client secret / private key is a backend credential. It never ships to a browser. Prefer
private_key_jwt: you hold the private key and Assetera registers only your public keys, so no shared
secret crosses the boundary.
Two grants
How you obtain a token depends on who is calling:
| Grant | Who | When |
|---|---|---|
| Authorization Code + PKCE | a user (via your BFF) | someone signs in and clicks through a UI |
| Client Credentials | your backend | server to server, no user present |
Both yield a signed JWT carrying your tenant claim; the grant only changes how the token is obtained, not
how the API validates it.
Getting a token (backend, Client Credentials)
curl -X POST \
"https://auth.<base_domain>/realms/assetera/protocol/openid-connect/token" \
-d grant_type=client_credentials \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \
-d audience=marketplace-api{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI...",
"token_type": "Bearer",
"expires_in": 300,
"scope": "marketplace.read"
}You then call the API with Authorization: Bearer <access_token>. Use
the discovery document
to resolve the real token, authorize, and jwks_uri endpoints for your environment rather than hardcoding
paths.
Inside the token
The access token is a signed JWT. The claims that matter to an integrator:
{
"iss": "https://auth.<base_domain>/realms/assetera",
"aud": "marketplace-api",
"sub": "b1e...-user-or-service-id",
"tenant": "your-tenant-id",
"scope": "marketplace.read marketplace.trade",
"exp": 1730000000
}Being finalised
The exact claim name for the tenant (tenant vs tenant_id), the scope catalogue, and the role model are
being finalised alongside marketplace-api development. Treat the names above as illustrative.
tenantscopes every response. It is minted by your client and read server-side; you cannot set or spoof it on the wire (see Tenancy).audpins the token to one API. A token for one service is rejected by another, so it cannot be replayed.expis short (minutes). Refresh (user flows) or re-request (M2M) rather than caching long-lived tokens.
How validation works
The API validates every token itself, against the realm's JWKS: signature, issuer, audience, expiry,
then it reads the tenant claim and scopes the query. If you gate your own UI, validate the same way in
your BFF, and always re-check authorisation on the server.
The interactive (user) flow
API reference
The integrator-facing APIs on Assetera: authentication, the Marketplace REST API, the Compliance API, and the planned real-time (SSE) service.
Marketplace API
The Marketplace / Trading REST API: base URL, bearer auth, and the known resources (tokens, token detail, candles). Evolving; OpenAPI forthcoming.