# Users

URL: https://docs.domainruntime.dev/docs/auth/users
Status: Planned
Reviewed: 2026-09-22



<div className="docs-note">
  **The mechanism runs today; the helpers are planned.**

   Minting user tokens works in production through the environment route shown under 

  *Today*

  . 

  `env.users.mintToken`

  , 

  `db.auth.signIn`

   and 

  `runtime.auth`

   are the planned helpers.
</div>

Your app keeps its own login — email, Google, Clerk, anything. After your backend has verified who the user is, it asks DomainRuntime for a **user token** for that environment. The browser signs in with it.

```text
Browser ──login──▶ your backend ──verifies──▶ your auth provider
                         │
                         └── mintToken({ id, email, claims }) ──▶ DomainRuntime
Browser ◀── user token ──┘
Browser ── db.auth.signIn(token) ──▶ <env>.domainruntime.cloud
```

## Mint a token on your backend [#mint-a-token-on-your-backend]

```ts title="app/api/session/route.ts"
import { init } from "@domainruntime/platform";

const platform = init({ serviceKey: process.env.DOMAIN_SERVICE_KEY! });
const env = platform.environments.get(process.env.DOMAIN_ENV!);   // e.g. "prod-acme-main"

export async function POST(request: Request) {
  const user = await requireSignedInUser(request);   // your auth provider's session check
  const { token } = await env.users.mintToken({
    id: user.id,
    email: user.email,
    claims: { role: user.role },
  });
  return Response.json({ token });
}
```

* `id` must be a UUID and stable for the person. Use the same id for the same person in every environment. If your provider's ids are not UUIDs (Clerk `user_…`, Auth0 `auth0|…`), derive a stable UUID from them, for example UUIDv5 over the provider id.
* `claims` are yours: whatever your actions and rules need, such as a role or a tenant. Up to 1 KB.
* A token is valid for one environment.

<details>
  <summary>
    Today
  </summary>

  Call the environment directly from your backend with its service credential:

  ```bash
  curl -X POST "https://<handle>.domainruntime.cloud/admin/refresh_tokens" \
    -H "authorization: Bearer $DOMAIN_SERVICE_KEY" -H "content-type: application/json" \
    -d '{"id": "3f0c…", "email": "ada@acme.com", "claims": {"role": "manager"}}'
  # { "user": { "id": "…", "email": "…", "refresh_token": "dru_…" }, "expiresAt": "…" }
  ```

  In the browser, sign the InstantDB client in with `db.auth.signInWithToken(refresh_token)`; see [Reading data](/docs/data/reading-data).
</details>

## Sign in from the browser [#sign-in-from-the-browser]

```ts
const { token } = await fetch("/api/session", { method: "POST" }).then((r) => r.json());
await db.auth.signIn(token);

db.auth.user;       // { id, email, claims } or null
await db.auth.signOut();
```

## Who is calling, on the server [#who-is-calling-on-the-server]

In an action, `runtime.auth.user` is the signed-in user or `null`. In [permission rules](/docs/auth/permissions), the same user is `auth`.

## Revoking [#revoking]

Signing out, or revoking a user from your backend, rejects that token's next request. Work the user already started — a running [workflow](/docs/data/workflows) — continues with the identity it was started with.

## The `$users` entity [#the-users-entity]

Signed-in users appear in `$users`; each user can see only their own row. Link your entities to it to model ownership:

```ts
orders_orderOwner: {
  forward: { on: "orders_order", has: "one", label: "owner" },
  reverse: { on: "$users", has: "many", label: "orders" },
},
```
