Authentication
June 18, 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 authenticate login— Interactive login (tries PKCE first, then falls back to Device Code).grace authenticate 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 /authenticate/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"
Authorization roles and creation boundaries
Authentication identifies the caller. Authorization decides whether that caller can act at a system, owner,
organization, repository, branch, or path scope. Use grace authorize list-roles for the live role catalog, and use the
full role IDs shown here when granting roles.
System support roles
SystemAdmin: full system administration, including system security administration. It includesSystemAdmin,SystemOperate, system read, and descendant owner, organization, repository, branch, path, approval, and webhook operations.SystemOperator: support-operator role for system-level operations and descendant administration. It includesSystemOperate, system read, and descendant admin/write/read operations. It does not includeSystemAdmin.SystemReader: support-reader role for system-wide visibility. It includes system read and descendant read operations. It does not includeSystemOperate, write, admin, or security administration operations.
SystemOperate is the system-level support operation permission. Owner creation requires either SystemAdmin or
SystemOperate on the system resource, so a support operator can create owners without also receiving system security
administration.
Canonical role IDs
Current role IDs use full scope names. Do not grant or document abbreviated Org* or Repo* names as current
contracts.
| Scope | Admin | Contributor / writer | Reader |
|---|---|---|---|
| System | SystemAdmin | SystemOperator | SystemReader |
| Owner | OwnerAdmin | OwnerContributor | OwnerReader |
| Organization | OrganizationAdmin | OrganizationContributor | OrganizationReader |
| Repository | RepositoryAdmin | RepositoryContributor | RepositoryReader |
| Branch | BranchAdmin | BranchWriter | BranchReader |
Approval-specific role IDs:
| Scope | Role ID | Allows |
|---|---|---|
| Repository | RepositoryApprovalResponder | Read and respond to repository approval requests |
| Branch | BranchApprovalResponder | Read and respond to branch approval requests |
role:ApprovalResponder remains a legacy approval selector compatibility alias. Do not use ApprovalResponder as a
grantable role ID.
Scope creation and creator-admin grants
Scope creation is authorized against the parent scope. When creation succeeds, Grace ensures the authenticated creator has the admin role on the newly created scope. If an inherited assignment already gives the creator admin authority, Grace does not need to persist a duplicate direct grant.
| Created scope | Required authorization on parent | Creator-admin role on new scope |
|---|---|---|
| Owner | SystemAdmin or SystemOperate on system | OwnerAdmin |
| Organization | OwnerAdmin or OwnerWrite on owner | OrganizationAdmin |
| Repository | OrganizationAdmin or OrganizationWrite on organization | RepositoryAdmin |
| Branch | RepositoryAdmin or RepositoryWrite on repository | BranchAdmin |
Creator-admin grants are live creation behavior, not migration or backfill behavior. They apply to the authenticated user principal that creates the scope. They are not granted to every group or claim that helped authorize the create request.
Non-scope creation does not create admin grants. For example, DirectoryVersion creation, directory save operations, reference creation through branch assign/checkpoint/commit/promote/save/tag/external flows, artifacts, promotion sets, reminders, validation records, and work items remain normal repository or branch write/admin operations. Creating those objects does not make the creator a repository, branch, or system admin.
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.
Debug/TestAuth mode also supports normal JWT bearer authentication when OIDC is configured. Scheme selection is deterministic:
- Grace PAT bearer tokens use
GracePat. - Non-PAT bearer tokens use OIDC/JWT validation when server OIDC settings are present.
- Explicit
x-grace-user-idrequests use TestAuth in testing mode. - Testing requests with no bearer token use TestAuth so local bootstrap can still challenge through the TestAuth path.
TestAuth does not trust arbitrary bearer tokens, and the CLI's GRACE_TOKEN environment variable remains PAT-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/authenticate/me" -Headers @{ "x-grace-user-id" = "dev-scott" }bash / zsh:
curl -H "x-grace-user-id: dev-scott" "http://localhost:5000/authenticate/me"
On first activation (with no existing assignments), Grace will seed SystemAdmin for dev-scott.
After that, bootstrap is a no-op.
The canonical zero-external-auth bootstrap helper uses this path and prints a usable PAT:
PowerShell:
pwsh ./scripts/start-debuglocal.ps1 -GraceServerUri "http://localhost:5000" -SkipAuthProbe
bash / zsh:
pwsh ./scripts/start-debuglocal.ps1 --GraceServerUri "http://localhost:5000" -SkipAuthProbe
-SkipAuthProbe is required for the true zero-OIDC bootstrap path because the default auth probe checks
/authenticate/oidc/config before TestAuth PAT creation. Omit -SkipAuthProbe when OIDC/MSA settings are configured
and you want the startup helper to verify both /authenticate/oidc/config and /authenticate/me before creating the
PAT.
If OIDC/MSA is configured instead, use the interactive CLI path:
PowerShell:
$env:GRACE_SERVER_URI="http://localhost:5000"
Remove-Item Env:GRACE_TOKEN -ErrorAction SilentlyContinue
grace authenticate login
grace authenticate whoami --output Verbose
grace authenticate token create --name "local-dev" --expires-in 30d --output Verbose
bash / zsh:
export GRACE_SERVER_URI="http://localhost:5000"
unset GRACE_TOKEN
grace authenticate login
grace authenticate whoami --output Verbose
grace authenticate token create --name "local-dev" --expires-in 30d --output Verbose
grace authenticate whoami proves authentication. grace authenticate token create requires an authenticated
principal that maps to a Grace User; it does not require an RBAC role before token creation.
MSA/OIDC authentication alone does not authorize first-time scope creation or other protected operations. For a fresh
local/debug environment, seed the first admin through grace__authz__bootstrap__system_admin_users or use an existing
admin to grant the needed role before creating owners, organizations, repositories, or branches. A PAT created before
RBAC grants will authenticate the principal, but it will not authorize protected operations until normal Grace
authorization permits them.
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).
Because GRACE_TOKEN wins before M2M and interactive login, a stale or revoked PAT can mask a fresh interactive
OIDC/MSA login. Unset GRACE_TOKEN when you want the CLI to use cached interactive credentials, and use
grace authenticate token status --output Verbose or grace doctor --check Authentication when the active auth source
unclear.
Primary CLI commands
The canonical command groups are grace authenticate and grace authorize. The CLI also accepts short aliases:
grace authn for authentication commands, grace authz for authorization commands, and root grace login /
grace logout aliases for the common interactive sign-in and sign-out flows.
-
grace authenticate 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 authenticate statusShows whether the CLI currently has usable credentials (PAT, M2M, or interactive). -
grace authenticate whoamiCalls the server and prints the authenticated identity information. -
grace authenticate logoutClears the cached interactive token from the secure store.
Equivalent aliases:
-
grace authn status -
grace authn whoami -
grace login -
grace logout -
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) as a mapped Grace User to create a PAT. PAT creation itself does not require an RBAC role; the resulting PAT still authorizes protected operations only through Grace's normal authorization checks.
CLI (recommended)
-
grace authenticate token create --name "<token-name>"Creates a PAT with the server-default lifetime. -
grace authenticate token create --name "<token-name>" --expires-in 30dCreates a PAT that expires after the given duration. Supported suffixes:s,m,h,d. -
grace authenticate 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 authenticate token listLists active PATs for the current principal. -
grace authenticate token list --allLists active + expired + revoked tokens. -
grace authenticate token revoke <token-id>Revokes a PAT by token ID (GUID). Revoked tokens are no longer accepted. -
grace authenticate 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 authorize grant-role ...Grants a role to a principal at a scope (Owner/Organization/Repository/Branch/System). -
grace authorize revoke-role ...Revokes a role from a principal at a scope. -
grace authorize list-role-assignments ...Lists role assignments at a scope (optionally filtered by principal). -
grace authorize upsert-path-permission ...Sets or updates a path permission entry in a repository. -
grace authorize remove-path-permission ...Removes a path permission entry. -
grace authorize list-path-permissions ...Lists path permissions, optionally scoped to a path prefix. -
grace authorize check ...Asks the server “would this principal be allowed to do operation X on resource Y?”
Equivalent short forms use the authz alias, for example grace authz check ....
For detailed role IDs and operations, use
grace authorize 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/authenticate/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 /authenticate/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 authenticate login— Interactive Auth0 login (tries PKCE, then device flow).
-
Verify:
grace authenticate whoami— Calls the server and prints the authenticated identity.
Server-side requirements for auto-configuration
For GET /authenticate/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 /authenticate/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 authenticate login— Interactive Auth0 login (tries PKCE, then device flow).
-
Verify:
grace authenticate 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 authenticate token create. Must be a Grace PAT (prefixgrace_pat_v1_). If set, this overrides interactive login and M2M. Unset stale or revoked values before troubleshooting interactive OIDC/MSA login. -
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 authenticate 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 authenticate status --output Verbose
grace authenticate token status --output Verbose
grace status --output Verbose
bash / zsh:
grace authenticate status --output Verbose
grace authenticate token status --output Verbose
grace status --output Verbose
If GRACE_TOKEN is false and interactive token is false, authenticate first:
PowerShell:
grace authenticate login --auth device
# or
grace authenticate login --auth pkce
bash / zsh:
grace authenticate login --auth device
# or
grace authenticate login --auth pkce
If you are using a PAT:
PowerShell:
$env:GRACE_TOKEN="grace_pat_v1_..."
grace authenticate token status --output Verbose
bash / zsh:
export GRACE_TOKEN="grace_pat_v1_..."
grace authenticate token status --output Verbose
If you are trying to use interactive OIDC/MSA login, make sure a stale PAT is not taking precedence:
PowerShell:
Remove-Item Env:GRACE_TOKEN -ErrorAction SilentlyContinue
grace authenticate status --output Verbose
grace authenticate whoami --output Verbose
bash / zsh:
unset GRACE_TOKEN
grace authenticate status --output Verbose
grace authenticate whoami --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 authorize check says denied
If grace authenticate whoami shows your expected GraceUserId, but:
grace authorize 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 authenticate whoami --output Json
grace authorize check --operation SystemAdmin --resource system --output Json
bash / zsh:
grace authenticate whoami --output Json
grace authorize 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 authorize grant-role.
Headless environments / CI
Use:
- M2M auth (client credentials env vars), or
- PATs (
GRACE_TOKEN), or grace authenticate login --auth deviceif interactive login is still acceptable.