aster.Web Data / Docs Aster UI ↗

Aster Web Data / Build an API

Authentication and access

Identify the caller, choose who may use each set, and limit each set to the rows a caller owns.

A notes service has one rule that matters most: each person sees their own notes and nobody else's. The server enforces it in three steps, all of them configuration: it identifies the caller, it decides which sets the caller may use, and it limits each set to the rows the caller owns.

The first step is auth.kind, which chooses how a request becomes a caller:

auth.kind Use it for The caller is
jwt Production, behind an identity provider The user a signed token names, with the token's roles and claims
dev-headers Local development only Whoever the X-Aster-User and X-Aster-Roles headers name
fixed A single-user deployment, or tests The one user and roles the configuration names, on every request
anonymous, the default Data that needs no sign-in Nobody: a set that needs a signed-in user answers 403 Forbidden

JWT

Set auth.kind to jwt to identify callers by a signed token in the Authorization header. The server checks the token's signature and its claims on every request, and the caller is whoever the token names:

auth:
  kind: jwt
  jwt:
    secret_env: AUTH_JWT_SECRET      # the HMAC secret, read from this environment variable
    audience: [notes]                # required: a token for another service is refused
    issuer: [https://idp.example.com]
    user_claim: sub                  # the default
    roles_claim: roles               # the default
    leeway_seconds: 60               # the default

A client sends the token with each request:

GET /odata/Note
Authorization: Bearer <token>

The server checks the signature with the secret named by secret_env, a PEM public key given by public_key_file, or a key set given by jwks_file. Name exactly one of the three. The secret is never written in the configuration file, and it must be at least 32 bytes long. Only the algorithms listed in algorithms are accepted, and none is never accepted. When algorithms is left out, it is the one the key implies: HS256 for a secret, and RS256, ES256 or EdDSA for an RSA, EC or Ed25519 PEM key. A key set needs algorithms listed.

The server requires an exp claim, checks nbf when the token has one, and checks aud and, when configured, iss. The user is the user_claim, and the roles are the roles_claim. Every other claim that is a string, a number or a boolean becomes an attribute of the caller. Rules read those attributes, for example AsterData.Claim('team'), as described on Rules.

A request with no token is anonymous. A request with a token the server cannot accept is 401 Unauthorized with a WWW-Authenticate: Bearer challenge. The server keeps no session: each request carries its own token.

With a token whose sub is alice, the request above returns only alice's notes. Without a token the same request is 403.

Development headers

For a local run without an identity provider, auth.kind: dev-headers reads the caller from X-Aster-User and X-Aster-Roles, a comma-separated list of roles. Any client can send those headers, so the server logs a warning at start-up. Use this setting for development only.

GET /odata/Note
X-Aster-User: alice
X-Aster-Roles: auditor

Per-set access

A set's security section says who may read, insert, update and delete it. Each operation takes one of three conditions:

services:
  shop:
    security:
      Product:
        read: {authenticated: true}
        insert: {roles: [catalogue]}
        update: {roles: [catalogue]}
        delete: {never: true}
      AuditLog:
        read: {roles: [auditor]}
        insert: {authenticated: true}
        update: {never: true}
        delete: {never: true}

The AuditLog example is an insert-only log. Signed-in callers add entries, and only auditors read them.

A caller the set's access refuses gets 403 wherever the set is read, including inside an expansion and in a path such as /Category(3)/products.

Row ownership

For a set whose rows belong to someone, name the owner property. A caller reads, updates and deletes only the rows whose owner is the caller:

services:
  shop:
    security:
      Note:
        read: {authenticated: true}
        insert: {authenticated: true}
        update: {authenticated: true}
        delete: {authenticated: true}
        owner: ownerId

The server sets ownerId on a new row to the caller, so a client cannot create a note for someone else. A write that names another owner is 403. A row that belongs to someone else looks absent: alice's request for bob's note returns 404, so the response does not reveal that the row exists.

A set can also let callers update or delete rows they cannot read, when the caller supplies If-Match: *. Name the set in updates_without_read or deletes_without_read. Both are opt-ins, and each one stays off unless the service lists the set.

Request context

A client sometimes works on one project at a time, and several browser tabs may each work on a different one. The client sends that choice with each request in the Aster-Context header, and the server keeps nothing between requests:

GET /odata/Note
Aster-Context: project=42, workspace="sales"

The value is an HTTP Structured Fields dictionary. A link or a download, which cannot set a header, sends the same text in the aster-context query parameter. The header wins when both are sent.

The service declares which values exist, and whether a value must be checked against the data:

services:
  shop:
    context:
      values:
        project:
          type: Int64
          required: false
          authorise:                  # checked against the data
            set: ProjectMember
            filter: "project eq context.project and member eq $user"
        workspace:
          type: String                # no authorising rule: unchecked
      user_source:
        set: Person                   # one row per user
        subject: login                # the property holding the token's subject
        required: false

A value with an authorise rule holds when a row of ProjectMember that the caller may read matches the filter. A request that names an undeclared value, the wrong type, or omits a required value gets 400. A header or parameter over 1 KiB, or with more than 16 entries, gets 431. A failed authorising rule gets 403 and names nothing.

A value with no authorising rule can be sent as anything by any caller. Do not use such a value for a security decision. The server logs a warning at start-up when a rule reads one.

Rules and operation criteria read the value as context.<name>. The user source gives each request the signed-in user's own row, which rules read as user.<property>. A client's $filter, $select and $apply cannot reach it, so the row does not appear in responses.

Next