Access Token

Table of Contents

This page defines the service architecture pattern Access Token in the pattern language. Every request carries a token that securely identifies the requester, and each service verifies it without calling the issuer. In ORE Studio the token is a JWT that IAM signs. It carries the tenant, the party, the visible parties and the permissions that row-level security and the permission checks use.

1. Also known as

  • Bearer token, in RFC 6750, which defines how a token travels on a request.
  • Session token, the name ORE Studio uses for the token a login returns.

2. Context

The platform is a set of services behind an edge, the HTTP gateway and the NATS bus. A request enters at the edge and can pass through several services before it reaches the store that answers it. Each service checks permissions, and the database applies row-level security by tenant and party.

3. Problem

How does every service that handles a request know who the requester is, and what the requester may do?

4. Forces

  • A call to the issuer on every request makes the issuer a bottleneck and a single point of failure.
  • A service must not trust an identity it cannot verify.
  • The identity must reach the database, because row-level security decides which rows a query sees.
  • An identity that cannot expire is a permanent credential once it leaks.

5. Solution

The issuer signs a token at login. The token states the subject, the tenant, the party, the visible parties, the permissions and an expiry. Every request carries it in the Authorization header. Each service verifies the signature with the issuer's public key, which it fetches once, and checks the expiry. It then builds a request context from the claims, checks the permission the operation needs, and sets the tenant and the party on the database session.

pattern_access_token.png

Figure 1: Access Token: the client logs in once, each service verifies the token with the issuer's public key, and the database filters rows by the tenant and the party in it.

Source: [[./pattern_access_token.puml][pattern_access_token.puml]]. Regenerate with plantuml -tpng doc/knowledge/service_architecture_patterns/pattern_access_token.puml.

The claims decide everything downstream. The request context takes them, the database session takes the tenant and the party, and the audit columns take the username.

pattern_access_token_claims.png

Figure 2: Access Token: the claims the token states, and where each one goes: the request context, the database session that row-level security reads, and the audit columns.

Source: [[./pattern_access_token_claims.puml][pattern_access_token_claims.puml]]. Regenerate with plantuml -tpng doc/knowledge/service_architecture_patterns/pattern_access_token_claims.puml.

6. Failure handling

A service refuses in a fixed order, and each refusal has its own code. Only token_expired is worth a retry, and only after a renewal.

pattern_access_token_verification.png

Figure 3: Access Token: the checks a service makes in order, the code each refusal carries, and the point where row-level security takes over.

Source: [[./pattern_access_token_verification.puml][pattern_access_token_verification.puml]]. Regenerate with plantuml -tpng doc/knowledge/service_architecture_patterns/pattern_access_token_verification.puml.

Concern What the pattern does
Errors A missing token answers unauthorized. A token without the permission answers forbidden. The reply has an empty body and an X-Error header, and a client reads the header before it decodes the body.
Timeouts Does not apply to verification, which is local. The key fetch at startup has its own timeout, and a service with no key refuses every request.
Retries An X-Error: token_expired reply is retried once, after a refresh. Every other refusal is final: the same token gets the same answer.
Idempotency Does not apply. Verification has no effect.
Observability The subject, the tenant and the party of the context are on every log line of the request. A refusal logs the reason and the subject.

7. Pitfalls

  • A refusal decoded as a body. A client that decodes the reply before it reads X-Error reports a decode error. The real cause is an authentication failure. See Anatomy of a Service.
  • The wrong algorithm at the edge. IAM signs with RS256. An edge that verifies another algorithm refuses every token, and the failure looks like a permission bug. See Service-to-service auth.
  • A second verifier "as a fallback". A shared secret beside the public key is a second way to be believed. It authenticates nothing legitimate and widens what a stolen secret can do.
  • A token stored as state. A token in a row, a queue archive or a workflow instance outlives its request and becomes a credential at rest.

8. Security

The token is a bearer credential: whoever holds it acts as its subject until it expires. Its lifetime bounds the cost of a leak, which is 30 minutes for a session token by default. Only IAM signs, so a service that is compromised cannot forge a token for another subject. The permissions in the token are a snapshot, so a revoked permission takes effect at the next token.

9. Resulting context

Benefits
Services verify identity with no call to IAM. The tenant and the party reach the database on every request, so row-level security applies to every read.
Liabilities
A token cannot be revoked before it expires. A change to an account's roles reaches its sessions only at their next token.

10. In ORE Studio

  • Signing. IAM signs with RS256 and publishes its public key through JWKS. The claims are jwt_claims in ores.security: subject, tenant, party, visible parties, roles, expiry.
  • Lifetime. token_settings in ores.iam.core: an access lifetime of 1800 seconds, renewal at 80% of the lifetime, and a session ceiling of 8 hours.
  • Verification. make_request_context in ores.service builds the request context, and has_permission checks a permission.
  • The edge. The HTTP gateway verifies with the same key and never signs. See Service-to-service auth.
  • The database. Multi-Tenancy Architecture sets the session's tenant and party, and PostgreSQL Row-Level Security filters on them.
  • Object storage. Storage policy mints an object capability the same way: a signed, short-lived token whose claims name the buckets and key prefixes a caller may touch, verified by the storage service against the same published key.

11. Related patterns

  • Client Credentials — how a service obtains an access token for itself.
  • Token Relay — how a service passes the caller's access token on.
  • Token Exchange — how a service obtains a different access token for a purpose.

12. Sources

13. See also

Emacs 29.3 (Org mode 9.6.15)