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:
- Auth0 (OIDC/JWT bearer tokens) for interactive developer login (PKCE or Device Code) and machine-to-machine (client credentials).
- 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.
Quickstart for contributors (recommended path)
- Set up Auth0 (one-time) using the instructions in Auth0 tenant setup below.
- Run Grace.Server (typically via Aspire) with OIDC env vars configured.
- 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"
-
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
SystemAdminrole assignments atScope.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_usersgrace__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
subclaim (exposed asgrace_user_id). - You can confirm the user ID with
GET /auth/me(GraceUserIdin 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 acceptstrueoryes)
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 tograce_claim.
Bootstrap as SystemAdmin without Auth0
-
Choose a local user ID (e.g.,
dev-scott). -
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" -
Send requests with the
x-grace-user-idheader.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:
-
An Auth0 API (Resource Server) for the Grace Server API
- This provides the Audience value (the API Identifier).
-
A Native Auth0 Application for the Grace CLI (interactive login)
- This provides the CLI client ID.
-
(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>/
- Used to construct the Authority:
-
API Identifier (your “Audience”)
- Example:
https://grace.local/api
- Example:
-
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:
-
Go to Applications → APIs → Create API.
-
Set:
- Name:
Grace (Dev)(or similar) - Identifier (this becomes the Audience): choose a stable string, e.g.
https://grace.local/api
- Name:
-
Enable Allow Offline Access on the API.
- This is required so the CLI can receive refresh tokens (when requesting
offline_access).
- This is required so the CLI can receive refresh tokens (when requesting
-
Save.
Record:
- API Identifier (Audience)
- Tenant Domain (Authority base)
Step 2: Create the Auth0 Native Application (Grace CLI)
Auth0 Dashboard steps:
-
Go to Applications → Applications → Create Application.
-
Choose application type: Native.
-
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).
-
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. -
-
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:
-
Create a new application of type Machine to Machine.
-
Authorize it to call your Grace API (Resource Server).
-
Choose scopes if you’ve defined API scopes (Grace does not currently require Auth0 API scopes for authorization decisions, but your tenant policies may).
-
Record:
- Client ID (
grace__auth__oidc__m2m_client_id) - Client Secret (
grace__auth__oidc__m2m_client_secret)
- Client ID (
Grace CLI authentication modes
The CLI can authenticate in multiple ways. The first matching mode “wins”:
- PAT mode if
GRACE_TOKENis set (must be a Grace PAT, prefixgrace_pat_v1_). - Error if
GRACE_TOKEN_FILEis set (local token storage is intentionally disabled). - M2M mode if M2M env vars are set.
- 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--authis not specified, the CLI attempts PKCE and falls back to Device Code. -
grace auth statusShows whether the CLI currently has usable credentials (PAT, M2M, or interactive). -
grace auth whoamiCalls the server and prints the authenticated identity information. -
grace auth logoutClears the cached interactive token from the secure store. -
grace doctor --check AuthenticationProduces a read-only diagnostic report for local authentication environment checks. Doctor parsesGRACE_TOKENshape without printing the token, reports unsupportedGRACE_TOKEN_FILEconfiguration, and checks OIDC environment completeness without acquiring, refreshing, storing, or validating credentials with a provider. Use specificauth.*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.
CLI (recommended)
-
grace auth token create --name "<token-name>"Creates a PAT with the server-default lifetime. -
grace auth token create --name "<token-name>" --expires-in 30dCreates a PAT that expires after the given duration. Supported suffixes:s,m,h,d. -
grace auth token create --name "<token-name>" --no-expiryCreates 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 listLists active PATs for the current principal. -
grace auth token list --allLists 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 statusShows whether the currentGRACE_TOKENis 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_claimvalues andgrace_group_idvalues 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-rolesand consult the authorization types inGrace.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. Usehttps://<tenant-domain>/. -
grace__auth__oidc__audience(required if...__authorityis 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__authorityandgrace__auth__oidc__audienceare 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:
- Recommended: have the CLI fetch OIDC settings from the Grace Server.
- Advanced: configure OIDC settings directly on the CLI via environment variables.
Recommended: CLI auto-configuration from the server
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)
-
Start the server with OIDC enabled (Authority + Audience, and preferably the CLI client ID).
-
Set the server URI:
PowerShell:
$env:GRACE_SERVER_URI="http://localhost:5000"bash / zsh:
export GRACE_SERVER_URI="http://localhost:5000" -
Login:
grace auth login— Interactive Auth0 login (tries PKCE, then device flow).
-
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__authority—https://<tenant-domain>/grace__auth__oidc__audience— Auth0 API Identifiergrace__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__authorityNo default. Source: Auth0 tenant domain. Format:https://<tenant-domain>/ -
grace__auth__oidc__audienceNo default. Source: Auth0 API Identifier (Resource Server “Identifier”). -
grace__auth__oidc__cli_client_idNo default. Source: Auth0 Native app Client ID.
Optional (recommended defaults)
-
grace__auth__oidc__cli_redirect_portDefault:8391If 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_scopesDefault:openid profile email offline_accessoffline_accessis required to receive refresh tokens.
How to use (client configuration)
-
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>" -
Login:
grace auth login— Interactive Auth0 login (tries PKCE, then device flow).
-
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__authorityNo default. Source: Auth0 tenant domain. -
grace__auth__oidc__audienceNo default. Source: Auth0 API Identifier. -
grace__auth__oidc__m2m_client_idNo default. Source: Auth0 M2M application Client ID. -
grace__auth__oidc__m2m_client_secretNo default. Source: Auth0 M2M application Client Secret.
Optional:
grace__auth__oidc__m2m_scopesDefault: 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 fromgrace auth token create. Must be a Grace PAT (prefixgrace_pat_v1_). If set, this overrides interactive login and M2M. -
GRACE_TOKEN_FILENot 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_daysDefault:90 -
grace__auth__pat__max_lifetime_daysDefault:365 -
grace__auth__pat__allow_no_expiryDefault: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 loginAuthenticates 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 --jsonLists tenants accessible to your Auth0 CLI session. -
auth0 tenant-settings show --jsonShows tenant settings (useful to confirm you’re operating on the expected tenant).
Find the Grace API audience (API Identifier)
-
auth0 apis list --jsonLists APIs (Resource Servers). Find your Grace API and read itsidentifier. -
auth0 apis show <api-id|api-audience> --jsonShows details for a specific API.
Find the CLI Client ID (Native app) and M2M credentials
-
auth0 apps list --jsonLists applications. -
auth0 apps show <app-id> --jsonShows app details (includingclient_id). -
auth0 apps show <app-id> --reveal-secrets --jsonShows 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_scopesincludesoffline_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_TOKENis 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 statusmaps tobranch status, which calls/branch/Getand/branch/GetParentBranch.- Those routes require authentication and are rejected with
401before 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/GetandPOST /branch/GetParentBranchwith401.
Also note:
--outputvalues are case-sensitive in current CLI behavior. UseVerbose, notverbose.- 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 JsonreturnsDenied: 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
serviceidthan 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_systemapplies 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_URIpoints to the server you think you are testing.- The running server's Orleans
serviceidmatches 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:
- Restart
Grace.Server(or your Aspire app host) to clear any stale grain state. - Re-run the two checks above.
- Re-open Cosmos and confirm the
systemAccessControl actor document for the active service context contains your user principal. - If needed, use a current
SystemAdminprincipal to grant your role again viagrace access grant-role.
Headless environments / CI
Use:
- M2M auth (client credentials env vars), or
- PATs (
GRACE_TOKEN), or grace auth login --auth deviceif interactive login is still acceptable.