Authentication

June 10, 2026 · View on GitHub

This document describes how to configure and use authentication for Grace during development.

Grace supports two authentication mechanisms:

  1. Auth0 (OIDC/JWT bearer tokens) for interactive developer login (PKCE or Device Code) and machine-to-machine (client credentials).
  2. Grace Personal Access Tokens (PATs) for automation and non-interactive usage.

Important: Authentication proves “who you are.” Authorization (RBAC and path permissions) determines “what you can do.” PATs do not introduce a separate permission model; they authenticate a principal that is then authorized via Grace’s normal authorization system.


  1. Set up Auth0 (one-time) using the instructions in Auth0 tenant setup below.
  2. Run Grace.Server (typically via Aspire) with OIDC env vars configured.
  3. Point the Grace CLI at your server by setting GRACE_SERVER_URI.

PowerShell:

$env:GRACE_SERVER_URI="http://localhost:5000"

bash / zsh:

export GRACE_SERVER_URI="http://localhost:5000"
  1. Login via the CLI:

    • grace auth login — Interactive login (tries PKCE first, then falls back to Device Code).
    • grace auth whoami — Verifies your identity against the running server.

Authorization bootstrap (SystemAdmin seeding)

Grace supports a one-time bootstrap mechanism to seed the first SystemAdmin role assignment in a fresh environment. This is required when you are standing up a new deployment that has no RBAC assignments yet.

How it works

  • Bootstrap runs only when the system-scope AccessControl actor first activates and has no existing assignments.
  • It reads configured bootstrap principals and creates SystemAdmin role assignments at Scope.System.
  • Bootstrap is one-time and will never overwrite or re-seed once any system-scope assignments exist.

Configuration

Set one or both of these environment variables (semicolon-delimited list):

  • grace__authz__bootstrap__system_admin_users
  • grace__authz__bootstrap__system_admin_groups

Important

  • The values must be principal IDs, not emails or display names.
  • For Auth0/OIDC, the user principal ID is taken from the sub claim (exposed as grace_user_id).
  • You can confirm the user ID with GET /auth/me (GraceUserId in the response).

PowerShell:

$env:grace__authz__bootstrap__system_admin_users="auth0|abc123"

bash / zsh:

export grace__authz__bootstrap__system_admin_users="auth0|abc123"

Development without Auth0 (TestAuth)

For local development, you can skip Auth0 entirely by enabling the built-in TestAuth handler. This is intended for dev/test only.

Enable TestAuth

Set:

  • GRACE_TESTING=1 (also accepts true or yes)

When enabled, Grace authenticates requests using headers:

  • x-grace-user-id — required; becomes the user principal ID (grace_user_id).
  • x-grace-claims — optional; semicolon-delimited values mapped to grace_claim.

Bootstrap as SystemAdmin without Auth0

  1. Choose a local user ID (e.g., dev-scott).

  2. Set bootstrap to that user ID:

    PowerShell:

    $env:grace__authz__bootstrap__system_admin_users="dev-scott"
    

    bash / zsh:

    export grace__authz__bootstrap__system_admin_users="dev-scott"
    
  3. Send requests with the x-grace-user-id header.

    PowerShell:

    Invoke-RestMethod "http://localhost:5000/auth/me" -Headers @{ "x-grace-user-id" = "dev-scott" }
    

    bash / zsh:

    curl -H "x-grace-user-id: dev-scott" "http://localhost:5000/auth/me"
    

On first activation (with no existing assignments), Grace will seed SystemAdmin for dev-scott. After that, bootstrap is a no-op.


Auth0 tenant setup (development)

What you need to create in Auth0

Grace needs these Auth0 resources:

  1. An Auth0 API (Resource Server) for the Grace Server API

    • This provides the Audience value (the API Identifier).
  2. A Native Auth0 Application for the Grace CLI (interactive login)

    • This provides the CLI client ID.
  3. (Optional) A Machine-to-Machine Auth0 Application for CI/automation

    • This provides an M2M client ID and M2M client secret.

Values you must capture from Auth0 (you will use these as env vars)

  • Tenant domain (example: my-tenant.us.auth0.com)

    • Used to construct the Authority: https://<tenant-domain>/
  • API Identifier (your “Audience”)

    • Example: https://grace.local/api
  • Native app Client ID (Grace CLI interactive login)

  • (Optional) M2M app Client ID + Client Secret


Step 1: Create the Auth0 API (Resource Server)

Auth0 Dashboard steps:

  1. Go to Applications → APIs → Create API.

  2. Set:

    • Name: Grace (Dev) (or similar)
    • Identifier (this becomes the Audience): choose a stable string, e.g. https://grace.local/api
  3. Enable Allow Offline Access on the API.

    • This is required so the CLI can receive refresh tokens (when requesting offline_access).
  4. Save.

Record:

  • API Identifier (Audience)
  • Tenant Domain (Authority base)

Step 2: Create the Auth0 Native Application (Grace CLI)

Auth0 Dashboard steps:

  1. Go to Applications → Applications → Create Application.

  2. Choose application type: Native.

  3. In the application settings:

    • Ensure Authorization Code (PKCE) is enabled.
    • Ensure Refresh Token grant is enabled.
    • Ensure Device Code grant is enabled (so the CLI can use device flow on headless systems).
  4. Configure Allowed Callback URLs to include the CLI callback URL:

    • Default Grace CLI callback URL:

      • http://127.0.0.1:8391/callback

    If you override the CLI redirect port (via grace__auth__oidc__cli_redirect_port), you must also update this callback URL accordingly.

  5. Configure refresh token behavior (recommended for development):

    • Enable refresh token rotation (or equivalent Auth0 setting) and ensure refresh tokens are issued to the application.

Record:

  • Native application Client ID (this is grace__auth__oidc__cli_client_id)

Step 3 (optional): Create the Auth0 M2M Application (automation)

Auth0 Dashboard steps:

  1. Create a new application of type Machine to Machine.

  2. Authorize it to call your Grace API (Resource Server).

  3. Choose scopes if you’ve defined API scopes (Grace does not currently require Auth0 API scopes for authorization decisions, but your tenant policies may).

  4. Record:

    • Client ID (grace__auth__oidc__m2m_client_id)
    • Client Secret (grace__auth__oidc__m2m_client_secret)

Grace CLI authentication modes

The CLI can authenticate in multiple ways. The first matching mode “wins”:

  1. PAT mode if GRACE_TOKEN is set (must be a Grace PAT, prefix grace_pat_v1_).
  2. Error if GRACE_TOKEN_FILE is set (local token storage is intentionally disabled).
  3. M2M mode if M2M env vars are set.
  4. Interactive mode if you have logged in previously (token stored in OS secure store).

Primary CLI commands

  • grace auth login [--auth pkce|device] Interactive login to Auth0; stores access/refresh tokens in the OS secure store. If --auth is not specified, the CLI attempts PKCE and falls back to Device Code.

  • grace auth status Shows whether the CLI currently has usable credentials (PAT, M2M, or interactive).

  • grace auth whoami Calls the server and prints the authenticated identity information.

  • grace auth logout Clears the cached interactive token from the secure store.

  • grace doctor --check Authentication Produces a read-only diagnostic report for local authentication environment checks. Doctor parses GRACE_TOKEN shape without printing the token, reports unsupported GRACE_TOKEN_FILE configuration, and checks OIDC environment completeness without acquiring, refreshing, storing, or validating credentials with a provider. Use specific auth.* check IDs when you want to run only one authentication diagnostic.


Personal Access Tokens (PATs)

PATs are bearer tokens issued by Grace and validated by Grace. They are typically used for:

  • CI jobs and automation
  • running the CLI in non-interactive environments
  • scripting against the Grace HTTP API

A PAT string looks like:

  • grace_pat_v1_<...>

Creating a PAT

You must already be authenticated (interactive Auth0 login, or an existing PAT, or M2M) to create a PAT.

  • grace auth token create --name "<token-name>" Creates a PAT with the server-default lifetime.

  • grace auth token create --name "<token-name>" --expires-in 30d Creates a PAT that expires after the given duration. Supported suffixes: s, m, h, d.

  • grace auth token create --name "<token-name>" --no-expiry Creates a non-expiring PAT only if the server allows it.

Notes

  • The PAT value is a secret. Store it in a secret manager.
  • Treat PATs like passwords; do not commit them into git.

Using a PAT

Set the token in your environment:

PowerShell:

$env:GRACE_TOKEN="grace_pat_v1_..."

bash / zsh:

export GRACE_TOKEN="grace_pat_v1_..."

Then run any CLI command as usual; authentication will use the PAT automatically.

If you are calling the HTTP API directly, use the standard Authorization header:

  • Authorization: Bearer grace_pat_v1_...

Listing and revoking PATs

  • grace auth token list Lists active PATs for the current principal.

  • grace auth token list --all Lists active + expired + revoked tokens.

  • grace auth token revoke <token-id> Revokes a PAT by token ID (GUID). Revoked tokens are no longer accepted.

  • grace auth token status Shows whether the current GRACE_TOKEN is present and parseable.

How PAT “permissions” work in Grace

PATs do not have an independent “permission set” like some systems (GitHub fine-grained tokens, etc.). Instead:

  • A PAT authenticates a principal (usually a user).

  • Grace authorization is then evaluated normally:

    • RBAC roles assigned to the principal (user or group) at a scope
    • Path permissions keyed by “claims” and/or group membership

Important implementation detail:

  • When a PAT is created, Grace snapshots the current principal’s grace_claim values and grace_group_id values into the token record on the server.
  • If your group membership or claim set changes later, existing PATs will not automatically pick up those changes. Create a new PAT if you need a token that reflects updated claims/groups.

Setting permissions for a PAT

Because a PAT’s authorization comes from the principal it authenticates, you “set PAT permissions” by granting/revoking roles and path permissions for that principal.

Primary CLI commands for authorization management:

  • grace access grant-role ... Grants a role to a principal at a scope (Owner/Organization/Repository/Branch/System).

  • grace access revoke-role ... Revokes a role from a principal at a scope.

  • grace access list-role-assignments ... Lists role assignments at a scope (optionally filtered by principal).

  • grace access upsert-path-permission ... Sets or updates a path permission entry in a repository.

  • grace access remove-path-permission ... Removes a path permission entry.

  • grace access list-path-permissions ... Lists path permissions, optionally scoped to a path prefix.

  • grace access check ... Asks the server “would this principal be allowed to do operation X on resource Y?”

For detailed role IDs and operations, use grace access list-roles and consult the authorization types in Grace.Types.Authorization.


Environment variables

This section documents auth-related environment variables used by Grace Server and the Grace CLI.

Grace CLI (always relevant)

  • GRACE_SERVER_URI (required for CLI) No default. Must point to the running Grace server base URI. Source: Aspire dashboard output, local run output, or your deployment URL.

Example value:

  • http://localhost:5000

PowerShell:

$env:GRACE_SERVER_URI="http://localhost:5000"

bash / zsh:

export GRACE_SERVER_URI="http://localhost:5000"

Grace Server OIDC configuration (Auth0/JWT)

These variables enable Auth0 JWT authentication on the server.

  • grace__auth__oidc__authority (optional, enables OIDC when set) No default. Source: your Auth0 tenant domain. Use https://<tenant-domain>/.

  • grace__auth__oidc__audience (required if ...__authority is set) No default. Source: Auth0 API Identifier (Resource Server “Identifier”).

Recommended (for CLI auto-config):

  • grace__auth__oidc__cli_client_id (optional for server auth; required for /auth/oidc/config) No default. Source: Auth0 Native app Client ID.

If grace__auth__oidc__authority and grace__auth__oidc__audience are not set, the server will fall back to PAT-only authentication.


Grace CLI interactive OIDC configuration (Auth0 login)

The Grace CLI can authenticate interactively using Auth0 (OIDC). There are two ways to supply the required OIDC settings:

  1. Recommended: have the CLI fetch OIDC settings from the Grace Server.
  2. Advanced: configure OIDC settings directly on the CLI via environment variables.

If you set only:

  • GRACE_SERVER_URI — Base URI of the running Grace Server (example: http://localhost:5000)

…then the CLI can fetch the OIDC configuration automatically by calling:

  • GET /auth/oidc/config — Returns the server’s OIDC settings needed for interactive login.

This works only if the server is configured with OIDC and has the values needed to publish them (see server env vars below).

How to use (server auto-configuration)
  1. Start the server with OIDC enabled (Authority + Audience, and preferably the CLI client ID).

  2. Set the server URI:

    PowerShell:

    $env:GRACE_SERVER_URI="http://localhost:5000"
    

    bash / zsh:

    export GRACE_SERVER_URI="http://localhost:5000"
    
  3. Login:

    • grace auth login — Interactive Auth0 login (tries PKCE, then device flow).
  4. Verify:

    • grace auth whoami — Calls the server and prints the authenticated identity.
Server-side requirements for auto-configuration

For GET /auth/oidc/config to return useful values, the server must be configured with:

  • grace__auth__oidc__authorityhttps://<tenant-domain>/
  • grace__auth__oidc__audience — Auth0 API Identifier
  • grace__auth__oidc__cli_client_id — Auth0 Native app Client ID (recommended)

If the server is missing grace__auth__oidc__cli_client_id, the CLI may still be able to login if you supply the client ID locally (see Advanced).


Advanced: set OIDC settings on the client

If you cannot use server auto-configuration (for example, you are testing against an endpoint that does not expose /auth/oidc/config), you can configure the CLI directly via environment variables.

Required
  • grace__auth__oidc__authority No default. Source: Auth0 tenant domain. Format: https://<tenant-domain>/

  • grace__auth__oidc__audience No default. Source: Auth0 API Identifier (Resource Server “Identifier”).

  • grace__auth__oidc__cli_client_id No default. Source: Auth0 Native app Client ID.

  • grace__auth__oidc__cli_redirect_port Default: 8391 If you change this, you must also update the Auth0 Native app callback URL to: http://127.0.0.1:<port>/callback

  • grace__auth__oidc__cli_scopes Default: openid profile email offline_access offline_access is required to receive refresh tokens.

How to use (client configuration)
  1. Set environment variables.

    PowerShell:

    $env:GRACE_SERVER_URI="http://localhost:5000"
    $env:grace__auth__oidc__authority="https://<tenant-domain>/"
    $env:grace__auth__oidc__audience="https://grace.local/api"
    $env:grace__auth__oidc__cli_client_id="<native-client-id>"
    

    bash / zsh:

    export GRACE_SERVER_URI="http://localhost:5000"
    export grace__auth__oidc__authority="https://<tenant-domain>/"
    export grace__auth__oidc__audience="https://grace.local/api"
    export grace__auth__oidc__cli_client_id="<native-client-id>"
    
  2. Login:

    • grace auth login — Interactive Auth0 login (tries PKCE, then device flow).
  3. Verify:

    • grace auth whoami — Calls the server and prints the authenticated identity.

Grace CLI machine-to-machine (M2M) configuration

If you set these env vars, the CLI will use Auth0 client credentials to obtain an access token.

  • grace__auth__oidc__authority No default. Source: Auth0 tenant domain.

  • grace__auth__oidc__audience No default. Source: Auth0 API Identifier.

  • grace__auth__oidc__m2m_client_id No default. Source: Auth0 M2M application Client ID.

  • grace__auth__oidc__m2m_client_secret No default. Source: Auth0 M2M application Client Secret.

Optional:

  • grace__auth__oidc__m2m_scopes Default: empty Space-separated list of scopes to request (if your tenant requires/uses them).

PowerShell:

$env:grace__auth__oidc__authority="https://<tenant-domain>/"
$env:grace__auth__oidc__audience="https://grace.local/api"
$env:grace__auth__oidc__m2m_client_id="<m2m-client-id>"
$env:grace__auth__oidc__m2m_client_secret="<m2m-client-secret>"
# Optional:
$env:grace__auth__oidc__m2m_scopes="read:foo write:bar"

bash / zsh:

export grace__auth__oidc__authority="https://<tenant-domain>/"
export grace__auth__oidc__audience="https://grace.local/api"
export grace__auth__oidc__m2m_client_id="<m2m-client-id>"
export grace__auth__oidc__m2m_client_secret="<m2m-client-secret>"
# Optional:
export grace__auth__oidc__m2m_scopes="read:foo write:bar"

Grace CLI PAT configuration

  • GRACE_TOKEN (optional) No default. Source: output from grace auth token create. Must be a Grace PAT (prefix grace_pat_v1_). If set, this overrides interactive login and M2M.

  • GRACE_TOKEN_FILE Not supported. Local plaintext token file storage is intentionally disabled.


Grace Server PAT policy controls

These affect how the server handles PAT creation requests:

  • grace__auth__pat__default_lifetime_days Default: 90

  • grace__auth__pat__max_lifetime_days Default: 365

  • grace__auth__pat__allow_no_expiry Default: false


Getting Auth0 values automatically (CLI + APIs)

If you already have an Auth0 tenant configured, the Auth0 CLI can be used to find the exact values needed for Grace without clicking through the dashboard.

Auth0 CLI login

  • auth0 login Authenticates the Auth0 CLI for interactive use (or with client credentials for CI).

If you need additional Management API scopes, re-run login with scopes, e.g.:

  • auth0 login --scopes "read:client_grants,create:client_grants"

Find tenant information

  • auth0 tenants list --json Lists tenants accessible to your Auth0 CLI session.

  • auth0 tenant-settings show --json Shows tenant settings (useful to confirm you’re operating on the expected tenant).

Find the Grace API audience (API Identifier)

  • auth0 apis list --json Lists APIs (Resource Servers). Find your Grace API and read its identifier.

  • auth0 apis show <api-id|api-audience> --json Shows details for a specific API.

Find the CLI Client ID (Native app) and M2M credentials

  • auth0 apps list --json Lists applications.

  • auth0 apps show <app-id> --json Shows app details (including client_id).

  • auth0 apps show <app-id> --reveal-secrets --json Shows app details including secrets (use only for M2M apps, and handle output carefully).

Make raw Management API calls (advanced)

  • auth0 api get "tenants/settings" Makes an authenticated request to the Auth0 Management API and prints JSON.

This is useful if you need endpoints not exposed by a dedicated auth0 <noun> <verb> command.

Creating Auth0 resources via the Auth0 CLI (optional)

You can create Auth0 resources non-interactively.

  • auth0 apis create ... Creates an API (Resource Server). You can set identifier (audience), token lifetime, and offline access.

  • auth0 apps create ... Creates an application (Native / M2M / etc).

  • auth0 apps update ... Updates an application (callbacks, grant types, refresh token config, etc).

If you prefer the dashboard, you can ignore this section entirely.


Troubleshooting

grace auth login fails and mentions refresh tokens

Ensure:

  • the Auth0 API has Allow Offline Access enabled
  • the Auth0 Native app allows refresh tokens and is configured to issue them
  • grace__auth__oidc__cli_scopes includes offline_access

grace status returns Unauthorized and Grace.Server logs look empty

This usually means the CLI sent branch status requests without a usable token.

Common causes:

  • No interactive token is cached yet.
  • GRACE_TOKEN is not set (or is invalid).
  • You are expecting endpoint handler logs, but the request is rejected by authentication middleware first.

Quick checks:

PowerShell:

grace auth status --output Verbose
grace auth token status --output Verbose
grace status --output Verbose

bash / zsh:

grace auth status --output Verbose
grace auth token status --output Verbose
grace status --output Verbose

If GRACE_TOKEN is false and interactive token is false, authenticate first:

PowerShell:

grace auth login --auth device
# or
grace auth login --auth pkce

bash / zsh:

grace auth login --auth device
# or
grace auth login --auth pkce

If you are using a PAT:

PowerShell:

$env:GRACE_TOKEN="grace_pat_v1_..."
grace auth token status --output Verbose

bash / zsh:

export GRACE_TOKEN="grace_pat_v1_..."
grace auth token status --output Verbose

Why logs may look empty:

  • grace status maps to branch status, which calls /branch/Get and /branch/GetParentBranch.
  • Those routes require authentication and are rejected with 401 before business handlers run.
  • You might see little or no endpoint-level logging for these failures.

Where to look for proof of 401:

  • W3C request logs under %TEMP%\Grace.Server.Logs (Windows) show request-level status codes.
  • Look for entries like POST /branch/Get and POST /branch/GetParentBranch with 401.

Also note:

  • --output values are case-sensitive in current CLI behavior. Use Verbose, not verbose.
  • During development, TestAuth may be available. See this file for setup details: docs/Authentication.md -> Development without Auth0 (TestAuth).

You can see SystemAdmin in Cosmos, but grace access check says denied

If grace auth whoami shows your expected GraceUserId, but:

  • grace access check --operation SystemAdmin --resource system --output Json returns Denied: missing permission SystemAdmin.,

and you can also see a SystemAdmin assignment document in Cosmos, the most common cause is a runtime context mismatch.

Typical mismatch causes:

  • The running server is using a different Orleans serviceid than the one that wrote the document.
  • You inspected a different Cosmos database or container than the running server is using.
  • The AccessControl grain loaded state before manual Cosmos edits (stale in-memory state until restart).

Why this happens:

  • System-scope role assignments are read from the AccessControl actor state keyed by scope and Orleans identity.
  • A document such as grace-dev__accesscontrolactor_system applies only to the matching Orleans service context.
  • If the active server context differs, authorization reads a different actor record.

What to verify first:

PowerShell:

grace auth whoami --output Json
grace access check --operation SystemAdmin --resource system --output Json

bash / zsh:

grace auth whoami --output Json
grace access check --operation SystemAdmin --resource system --output Json

Then verify server configuration:

  • GRACE_SERVER_URI points to the server you think you are testing.
  • The running server's Orleans serviceid matches the service prefix of the AccessControl actor document.
  • The running server's Cosmos database/container match the place where you inspected the document.

Recommended recovery steps:

  1. Restart Grace.Server (or your Aspire app host) to clear any stale grain state.
  2. Re-run the two checks above.
  3. Re-open Cosmos and confirm the system AccessControl actor document for the active service context contains your user principal.
  4. If needed, use a current SystemAdmin principal to grant your role again via grace access grant-role.

Headless environments / CI

Use:

  • M2M auth (client credentials env vars), or
  • PATs (GRACE_TOKEN), or
  • grace auth login --auth device if interactive login is still acceptable.