5.1 The auth service: login, refresh, logout, current user

auth provides identity for a tenant: it turns credentials into a token, and the runtime checks that token (and the roles it carries) on every request. This section covers the endpoints; the next two cover users/roles and how mounts use them.

Enabling auth

Two things turn auth on for a tenant:

  1. A tenant-level auth object with a signing key:

    "auth": { "jwtSecret": "<required>", "sessionMinutes": 60,
              "maxAttempts": 5, "lockMinutes": 10, "userDataset": "users",
              "jwtUserProps": ["accountId"] }
    
  2. An auth mount (conventionally at /auth):

    { "path": "/auth", "service": "auth",
      "config": { "access": { "read": "authenticated", "invoke": "all" } } }
    

Once auth.jwtSecret is set, the runtime verifies a presented token on every request to the tenant — not just the auth mount.

The endpoints

Endpoint Does
POST /auth/login {email, password} 200 {token, exp} and a Set-Cookie: rs-auth=… (HttpOnly). Failures are a uniform 401 (no user enumeration); after maxAttempts failures the account locks for lockMinutes (401 + Retry-After)
POST /auth/refresh With a valid token past 50% of its lifetime, issues a fresh one; earlier than that, echoes the current token
POST /auth/logout 204, clears the cookie
GET /auth/user → the verified principal {id, roles, kind, …extra claims} (401 if anonymous)

Logging in

$r = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:3100/auth/login" `
       -ContentType "application/json" `
       -Body (@{ email = "ada@example.com"; password = $pw } | ConvertTo-Json)
$token = $r.token

You then authenticate subsequent requests with either the cookie (browsers get it automatically) or a bearer header:

Authorization: Bearer <token>

Keep the login mount reachable

Login and refresh are POSTs, so read doesn't gate them — they map to invoke. Make sure the auth mount's access doesn't lock out invoke. The simplest safe posture is invoke: "all" so anyone can attempt to log in; the credentials themselves are the gate. Do not omit access: an absent policy fails closed.

Bootstrapping the first operator

The first operator is a deliberate root-of-trust step: they cannot be created through an API that already requires an operator. On a fresh node whose /services mount is temporarily open, the CLI can do the whole sequence:

rs2 auth init --admin-email admin@example.com

It enables auth, creates the first admin with a locally generated argon2id hash, logs in, and locks down the user store and /services. Keep the node bound to 127.0.0.1 during this short bootstrap window. Operators can instead seed one user at server startup with bootstrapAdmin (10.7).

jwtUserProps optionally copies named user-record fields into the JWT and the principal returned by /auth/user. Those claims are also available to pipeline path rules and transforms. Treat them as authority for the token's lifetime: only copy fields that users cannot edit themselves.

A subtlety worth knowing now

Because the runtime verifies any presented token, an invalid or expired token is 401 even on an open mount that would otherwise accept anonymous callers. The fix is to strip a bad token and retry anonymously, not to resend it. (More on this in 5.3.)

Next: the users and roles those tokens represent.


Previous: 5.0 The RS2 security model · Manual home · Next: 5.2 Users, password hashes, roles →