Migration Guide: Authorizer v1 to v2
This page helps you migrate from Authorizer v1 to Authorizer v2. The v2 server focuses on CLI-based configuration, better secret handling, and deployment hardening.
1. Major Security and Configuration Change
What changed
In v1, environment and configuration could be:
- Loaded from
.envor OS environment variables. - Stored and updated in cache (e.g. Redis) or database via the dashboard or
_update_envmutation.
In v2:
- All configuration is passed at server start via CLI root arguments.
- Nothing is read from a persisted "env store" in cache or DB.
- Secrets and config are not stored in your database or cache. They are supplied explicitly at startup (for example via your orchestrator, platform env to CLI args, or a wrapper script).
What you need to do
-
Copy all your existing v1 credentials and environment configuration BEFORE migrating.
Critical — do not skip this stepYou must capture all your v1 env variables before migrating. Missing values will cause the v2 server to fail or behave incorrectly, and you will not be able to recover them after shutting down v1.
How to get your current env variables:
-
Option 1: Using the
_envquery (recommended)Query the
_envGraphQL field with your admin secret to export all current configuration:curl --location 'YOUR_AUTHORIZER_URL/graphql' \
--header 'x-authorizer-admin-secret: YOUR_ADMIN_SECRET' \
--header 'Content-Type: application/json' \
--data '{
"query": "{\n _env {\n CLIENT_ID\n CLIENT_SECRET\n GOOGLE_CLIENT_ID\n GOOGLE_CLIENT_SECRET\n GITHUB_CLIENT_ID\n GITHUB_CLIENT_SECRET\n FACEBOOK_CLIENT_ID\n FACEBOOK_CLIENT_SECRET\n LINKEDIN_CLIENT_ID\n LINKEDIN_CLIENT_SECRET\n APPLE_CLIENT_ID\n APPLE_CLIENT_SECRET\n DISCORD_CLIENT_ID\n DISCORD_CLIENT_SECRET\n TWITTER_CLIENT_ID\n TWITTER_CLIENT_SECRET\n MICROSOFT_CLIENT_ID\n MICROSOFT_CLIENT_SECRET\n MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID\n TWITCH_CLIENT_ID\n TWITCH_CLIENT_SECRET\n ROBLOX_CLIENT_ID\n ROBLOX_CLIENT_SECRET\n DEFAULT_ROLES\n PROTECTED_ROLES\n ROLES\n JWT_TYPE\n JWT_SECRET\n JWT_ROLE_CLAIM\n JWT_PRIVATE_KEY\n JWT_PUBLIC_KEY\n REDIS_URL\n SMTP_HOST\n SMTP_PORT\n SMTP_USERNAME\n SMTP_PASSWORD\n SMTP_LOCAL_NAME\n SENDER_EMAIL\n SENDER_NAME\n ALLOWED_ORIGINS\n ORGANIZATION_NAME\n ORGANIZATION_LOGO\n ADMIN_SECRET\n APP_COOKIE_SECURE\n ADMIN_COOKIE_SECURE\n DISABLE_LOGIN_PAGE\n DISABLE_MAGIC_LINK_LOGIN\n DISABLE_EMAIL_VERIFICATION\n DISABLE_BASIC_AUTHENTICATION\n DISABLE_MOBILE_BASIC_AUTHENTICATION\n DISABLE_SIGN_UP\n DISABLE_STRONG_PASSWORD\n DISABLE_REDIS_FOR_ENV\n CUSTOM_ACCESS_TOKEN_SCRIPT\n DATABASE_NAME\n DATABASE_TYPE\n DATABASE_URL\n ACCESS_TOKEN_EXPIRY_TIME\n DISABLE_MULTI_FACTOR_AUTHENTICATION\n ENFORCE_MULTI_FACTOR_AUTHENTICATION\n DEFAULT_AUTHORIZE_RESPONSE_TYPE\n DEFAULT_AUTHORIZE_RESPONSE_MODE\n DISABLE_PLAYGROUND\n DISABLE_TOTP_LOGIN\n DISABLE_MAIL_OTP_LOGIN\n __typename\n }\n}",
"variables": {}
}' -
Option 2: Copy from the v1 dashboard
Go through your v1 dashboard settings and copy every value you configured. This includes:
- OAuth / app:
client_id,client_secret,admin_secret - Social / OAuth providers: Google, GitHub, Facebook, Microsoft, Apple, LinkedIn, Discord, Twitter, Twitch, Roblox client IDs and secrets
- Roles:
roles,default_roles,protected_roles - JWT:
jwt_type,jwt_secret(orjwt_private_key/jwt_public_key) - Session / memory store:
redis_url(if using Redis) - Email / SMTP:
smtp_host,smtp_port,smtp_username,smtp_password,smtp_sender_email,smtp_sender_name - Domain / origins:
allowed_origins - Access token custom scripts:
custom_access_token_script
- OAuth / app:
You will need to pass each of these as a CLI flag in v2 for a smooth transition.
-
-
Stop relying on dashboard or
_update_envfor server configuration. In v2, the server does not load or save config from/to DB or cache. Configure everything when starting the server. -
Map your current v1 env vars to v2 CLI flags. Use the configuration mapping below and pass options when starting the binary (see Running the server).
-
Ensure required flags are set at startup. The v2 server will fail to start if critical flags are missing. At minimum you must provide:
--database-typeand--database-url(or individual--database-host,--database-port,--database-name,--database-username,--database-password) — the server cannot start without a database connection.--client-idand--client-secret— required; the server will exit if they are missing.--admin-secret— needed for admin dashboard access and admin API operations.--jwt-typeand--jwt-secret(for HMAC algorithms like HS256) or--jwt-private-key/--jwt-public-key(for RSA/ECDSA algorithms) — needed for token signing and verification.
2. Running the Server
v1 (typical)
# .env or OS env
export DATABASE_TYPE=sqlite
export DATABASE_URL=data.db
export CLIENT_ID=...
export CLIENT_SECRET=...
./build/server
Or configure via dashboard after first run.
v2 (recommended)
Pass all config as CLI arguments when starting the server:
./build/server \
--database-type=sqlite \
--database-url=data.db \
--client-id=YOUR_CLIENT_ID \
--client-secret=YOUR_CLIENT_SECRET \
--admin-secret=your-admin-secret \
--jwt-type=HS256 \
--jwt-secret=your-jwt-secret
For local development (from repo root):
make dev
# or
go run main.go --database-type=sqlite --database-url=test.db \
--jwt-type=HS256 --jwt-secret=test --admin-secret=admin \
--client-id=123456 --client-secret=secret
Using environment variables with v2
The v2 server does not read from .env or from a fixed set of OS env vars.
To keep using env vars in your deployment:
-
Option A: Set env vars in your platform (Docker, K8s, Railway, etc.) and pass them into the process as arguments via a wrapper script or
envsubst:./build/server \
--database-type="$DATABASE_TYPE" \
--database-url="$DATABASE_URL" \
--client-id="$CLIENT_ID" \
--client-secret="$CLIENT_SECRET" \
... -
Option B: Use your platform's way of injecting env into the command line (for example Docker
CMDor Kubernetes command/args that reference env).
Example Docker run:
docker run -p 8080:8080 \
-e DATABASE_TYPE=postgres \
-e DATABASE_URL="postgres://..." \
-e CLIENT_ID=... \
-e CLIENT_SECRET=... \
your-authorizer-image \
./build/server \
--database-type="$DATABASE_TYPE" \
--database-url="$DATABASE_URL" \
--client-id="$CLIENT_ID" \
--client-secret="$CLIENT_SECRET" \
--admin-secret="$ADMIN_SECRET"
Build from source (v2)
Prerequisites
- Go >= 1.24 (see
go.modin the main repo). - Node.js >= 18 and npm / pnpm / yarn (only required if you want to build the web UIs).
Steps
-
Clone the repo
git clone https://github.com/authorizerdev/authorizer.git
cd authorizer -
Build the server binary
go build -o build/authorizer . -
(Optional) Build the web app and dashboard
cd web/app && npm ci && npm run build
cd ../dashboard && npm ci && npm run build
cd ../.. # back to repo root -
Run the server with CLI args
./build/authorizer \
--database-type=sqlite \
--database-url=data.db \
--client-id=YOUR_CLIENT_ID \
--client-secret=YOUR_CLIENT_SECRET \
--admin-secret=your-admin-secret \
--jwt-type=HS256 \
--jwt-secret=your-jwt-secret
3. Configuration Mapping (v1 env / v1 behavior to v2 CLI flags)
Use these v2 CLI flags instead of v1 env or dashboard config. Flag names use kebab-case (for example --database-url).
Deprecated v1-style flag names (do not use)
database_url-- use--database-urldatabase_type-- use--database-typeenv_file-- no longer supportedlog_level-- use--log-levelredis_url-- use--redis-url
Core / server
| v1 (env or behavior) | v2 CLI flag |
|---|---|
ENV | --env |
PORT | --http-port (default: 8080) |
| Host | --host (default: 0.0.0.0) |
| Metrics port | --metrics-port (default: 8081) |
| Metrics bind | --metrics-host (default: 127.0.0.1) for the dedicated metrics listener |
LOG_LEVEL | --log-level |
GET /metrics is always on the dedicated metrics listener at --metrics-host:--metrics-port (default loopback); --http-port and --metrics-port must differ. Health probes remain on the HTTP port. For in-cluster Prometheus, set --metrics-host=0.0.0.0 and scrape over the private network.
Database
| v1 | v2 CLI flag |
|---|---|
DATABASE_TYPE | --database-type |
DATABASE_URL | --database-url |
DATABASE_NAME | --database-name |
DATABASE_USERNAME | --database-username |
DATABASE_PASSWORD | --database-password |
DATABASE_HOST | --database-host |
DATABASE_PORT | --database-port |
DATABASE_CERT, DATABASE_CA_CERT, DATABASE_CERT_KEY | --database-cert, --database-ca-cert, --database-cert-key |
| Couchbase | --couchbase-bucket, --couchbase-scope, --couchbase-ram-quota |
| AWS/DynamoDB | --aws-region, --aws-access-key-id, --aws-secret-access-key |
Memory store (sessions)
| v1 | v2 CLI flag |
|---|---|
REDIS_URL | --redis-url |
OAuth / app
| v1 | v2 CLI flag |
|---|---|
CLIENT_ID | --client-id (required) |
CLIENT_SECRET | --client-secret (required) |
ADMIN_SECRET | --admin-secret |
ALLOWED_ORIGINS | --allowed-origins (slice; default *) |
DEFAULT_AUTHORIZE_RESPONSE_TYPE / MODE | --default-authorize-response-type, --default-authorize-response-mode |
Organization / UI
| v1 | v2 CLI flag |
|---|---|
ORGANIZATION_NAME | --organization-name |
ORGANIZATION_LOGO | --organization-logo |
DISABLE_LOGIN_PAGE | --enable-login-page (inverted: use false to disable) |
DISABLE_PLAYGROUND | --enable-playground (inverted: use false to disable) |
| N/A (GraphQL introspection always on) | --enable-graphql-introspection (default true; set false to disable schema introspection in hardened environments) |
Auth behavior
| v1 | v2 CLI flag |
|---|---|
| Roles | --roles, --default-roles, --protected-roles |
DISABLE_STRONG_PASSWORD | --enable-strong-password (inverted) |
DISABLE_BASIC_AUTHENTICATION | --enable-basic-authentication (inverted) |
DISABLE_EMAIL_VERIFICATION | --enable-email-verification (inverted) |
DISABLE_MAGIC_LINK_LOGIN | --enable-magic-link-login (inverted) |
ENFORCE_MULTI_FACTOR_AUTHENTICATION | --enforce-mfa, --enable-mfa |
DISABLE_SIGN_UP | --enable-signup (inverted) |
| TOTP / OTP | --enable-totp-login, --enable-email-otp, --enable-sms-otp |
| Mobile basic auth | --enable-mobile-basic-authentication |
| Phone verification | --enable-phone-verification |
Cookies
| v1 | v2 CLI flag |
|---|---|
APP_COOKIE_SECURE, ADMIN_COOKIE_SECURE | --app-cookie-secure, --admin-cookie-secure |
JWT
| v1 | v2 CLI flag |
|---|---|
JWT_TYPE | --jwt-type |
JWT_SECRET | --jwt-secret |
JWT_PRIVATE_KEY, JWT_PUBLIC_KEY | --jwt-private-key, --jwt-public-key |
JWT_ROLE_CLAIM | --jwt-role-claim |
CUSTOM_ACCESS_TOKEN_SCRIPT | --custom-access-token-script |
SMTP
| v1 | v2 CLI flag |
|---|---|
SMTP_HOST, SMTP_PORT | --smtp-host, --smtp-port |
SMTP_USERNAME, SMTP_PASSWORD | --smtp-username, --smtp-password |
SENDER_EMAIL, SENDER_NAME | --smtp-sender-email, --smtp-sender-name |
SMTP_LOCAL_NAME | --smtp-local-name |
| Skip TLS verify | --skip-tls-verification |
Twilio (SMS)
| v1 | v2 CLI flag |
|---|---|
TWILIO_ACCOUNT_SID, TWILIO_API_KEY, TWILIO_API_SECRET, TWILIO_SENDER | --twilio-account-sid, --twilio-api-key, --twilio-api-secret, --twilio-sender |
Social / OAuth providers
Each provider is configured with --<provider>-client-id, --<provider>-client-secret, and optionally --<provider>-scopes, for example:
--google-client-id,--google-client-secret,--google-scopes--github-client-id,--github-client-secret,--github-scopes--facebook-client-id,--facebook-client-secret,--facebook-scopes--microsoft-client-id,--microsoft-client-secret,--microsoft-tenant-id,--microsoft-scopes--apple-client-id,--apple-client-secret,--apple-scopes--linkedin-client-id,--linkedin-client-secret,--linkedin-scopes--discord-client-id,--discord-client-secret,--discord-scopes--twitter-client-id,--twitter-client-secret,--twitter-scopes--twitch-client-id,--twitch-client-secret,--twitch-scopes--roblox-client-id,--roblox-client-secret,--roblox-scopes
Other
| v1 | v2 CLI flag |
|---|---|
RESET_PASSWORD_URL | --reset-password-url |
Admin / GraphQL security flags (v2-only)
The following flags are new in v2 and help harden your deployment:
--disable-admin-header-auth: when set totrue, the server does not acceptX-Authorizer-Admin-Secretas admin authentication; only the secure admin cookie is honored.- Recommended for production:
--disable-admin-header-auth=true.
- Recommended for production:
--enable-graphql-introspection: controls whether GraphQL introspection is enabled on/graphql.- Default is
true(for development and tooling). - For locked-down production, you can set
--enable-graphql-introspection=falseto prevent unauthenticated schema discovery.
- Default is
To see all flags and defaults:
./build/server --help
Breaking changes — April 2026 security batch
If you are upgrading across the April 2026 security release (any release on or after that date), two existing flags now have stricter behaviour. Both will cause silent regressions if you don't address them during the upgrade:
--admin-secret is now required
In earlier v2 releases, --admin-secret defaulted to the literal string
password if you forgot to set it. That default is gone. The server
now exits at startup with a fatal error when the flag is empty or
missing:
FATAL: --admin-secret is required and must not be empty.
Action required: set --admin-secret to any non-empty value before
restarting. The strength of the secret is your responsibility — the
server only enforces non-emptiness.
./build/server --admin-secret="$(openssl rand -hex 32)" ...
--trusted-proxies defaults to none
Per-IP rate limiting, audit logs, Prometheus metrics, and CSRF
same-origin checks now read the client IP from RemoteAddr by default
and ignore X-Forwarded-For. This closes a spoofing hole where any
client could pretend to be a different IP by sending a forged
X-Forwarded-For header.
If your Authorizer instance is directly exposed to the internet (no proxy in front), you don't need to do anything — the new default is correct.
If your Authorizer instance is behind a reverse proxy (nginx, AWS ALB, Cloudflare, an ingress controller, etc.), you must opt in by listing the proxy network in CIDR form. Otherwise, every request will appear to come from the proxy IP and per-IP rate limiting will trip on its first burst:
# Behind nginx on the same host
./build/server --trusted-proxies=127.0.0.1/32,::1/128 ...
# Inside a Kubernetes cluster
./build/server --trusted-proxies=10.0.0.0/8 ...
# Behind Cloudflare
./build/server --trusted-proxies=$(cat cloudflare-ips.txt | paste -sd, -) ...
See the new Security Hardening page for the full topology table and flag reference.
Database schema: email / phone_number are no longer UNIQUE
In v1 the email and phone_number columns of the authorizer_users and
authorizer_otps tables had database-level UNIQUE constraints. In v2 they
are plain (non-unique) indexes, and uniqueness is enforced in the
application layer instead (the create/update paths reject a duplicate email
or phone number with user with given email already exists).
Why the change:
- Uniform behaviour across all 13+ databases. A SQL
UNIQUEconstraint only exists on the relational backends and treatsNULLs differently per engine. Enforcing in code gives identical semantics on Postgres, MySQL, MongoDB, DynamoDB, Cassandra, etc. emailandphone_numberare now optional. v2 supports email-only and phone-only signups, so a user row may legitimately have aNULLemail or phone number — a hardUNIQUEconstraint on a nullable column is awkward and inconsistent.
Symptom on upgrade
When v2 runs its automatic schema migration against a database created by v1,
GORM detects the columns are still unique and tries to drop the old
constraint — but under its own generated name (uni_<table>_<column>), which
does not match the name Postgres/MySQL actually assigned
(<table>_<column>_key). The migration then aborts at startup:
ERROR: constraint "uni_authorizer_users_email" of relation "authorizer_users" does not exist (SQLSTATE 42704)
ALTER TABLE "authorizer_users" DROP CONSTRAINT "uni_authorizer_users_email"
failed to create storage provider
You may see the same error for authorizer_otps, and for the phone_number
column of either table (uni_authorizer_users_phone_number,
uni_authorizer_otps_email, uni_authorizer_otps_phone_number).
Depending on which v1 release created your database, the old uniqueness can take any of these forms:
- a UNIQUE constraint under almost any name —
authorizer_users_email_key(the Postgres default),idx_authorizer_otps_phone_number(a v1gorm:"uniqueIndex"that the database promoted to a constraint), or a custom name from a hand-rolled migration; or - a standalone UNIQUE index created with
CREATE UNIQUE INDEX.
A constraint's backing index cannot be removed with DROP INDEX — Postgres
rejects it with cannot drop index ... because constraint ... requires it. Use
DROP CONSTRAINT for those (the index disappears with the constraint).
Fix
Authorizer drops these stale unique objects automatically at startup — by their real names, whatever they are — before running the migration. On a current build no action is required; just upgrade to the latest server.
If you are pinned to an older build that still fails with the error above, clear them manually once, then restart.
1. List the real names of the single-column unique objects on these columns:
-- Postgres / Yugabyte / CockroachDB
-- (a) UNIQUE constraints — drop each with ALTER TABLE ... DROP CONSTRAINT
SELECT conrelid::regclass AS "table", conname AS name
FROM pg_constraint
WHERE conrelid IN ('authorizer_users'::regclass, 'authorizer_otps'::regclass)
AND contype = 'u';
-- (b) standalone UNIQUE indexes (NOT backed by a constraint) — drop with DROP INDEX
SELECT t.relname AS "table", i.relname AS name
FROM pg_index ix
JOIN pg_class i ON i.oid = ix.indexrelid
JOIN pg_class t ON t.oid = ix.indrelid
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY (ix.indkey)
WHERE t.relname IN ('authorizer_users', 'authorizer_otps')
AND ix.indisunique AND NOT ix.indisprimary
AND a.attname IN ('email', 'phone_number')
AND NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conindid = ix.indexrelid);
2. Drop them — DROP CONSTRAINT for every name from query (a), DROP INDEX
for every name from query (b). For example:
ALTER TABLE authorizer_users DROP CONSTRAINT IF EXISTS authorizer_users_email_key;
ALTER TABLE authorizer_otps DROP CONSTRAINT IF EXISTS idx_authorizer_otps_phone_number;
-- ...one line per name from query (a), across both tables, then for query (b):
DROP INDEX IF EXISTS idx_authorizer_otps_email;
On MySQL / MariaDB these are unique indexes — list them with
SHOW INDEX FROM authorizer_users WHERE Non_unique = 0; and drop with
ALTER TABLE authorizer_users DROP INDEX <name>;. SQLite and the NoSQL
backends are unaffected.
This only removes a constraint that v2 no longer uses. Duplicate emails and phone numbers are still rejected — the check now lives in the server, not the database.
4. Deprecated GraphQL API Behavior
These mutations exist for compatibility but return an error in v2; configure via CLI instead.
| Mutation | v2 behavior |
|---|---|
_update_env | Returns error: "deprecated. please configure env via cli args" |
_admin_signup | Returns error: "deprecated. please configure admin secret via cli args" |
_generate_jwt_keys | Returns error: "deprecated. please configure jwt keys via cli args" |
- Admin secret: set with
--admin-secretat startup. - JWT keys/type: set with
--jwt-type,--jwt-secret, or--jwt-private-key/--jwt-public-keyat startup. - All other env: use the corresponding CLI flags when starting the server.
If your app or dashboard calls _update_env, _admin_signup, or _generate_jwt_keys, remove or replace those calls and move configuration to startup arguments.
5. Docker changes in v2
- The v2 image uses ENTRYPOINT so the server receives CLI arguments at runtime.
- Do not rely on env vars being read directly by the server; pass config as arguments to the container.
Example:
ENTRYPOINT [ "./build/server" ]
CMD []
Run with args:
docker run -p 8080:8080 your-image \
--database-type=postgres \
--database-url="postgres://user:pass@host/db" \
--client-id=... \
--client-secret=... \
--admin-secret=...
Or use a script inside the image that maps env to flags and then runs ./build/server ....
6. SDK and Client Libraries
@authorizerdev/authorizer-js (v3)
- Version: v2 uses authorizer-js
3.0.0-rc.1(or compatible v3). - Type renames (breaking):
SignupInputtoSignUpRequestLoginInputtoLoginRequestVerifyOtpInputtoVerifyOTPRequestMagicLinkLoginInputtoMagicLinkLoginRequest
- Build/output: CJS/ESM paths may differ; check the package
exportsand your bundler.
Upgrade:
npm install @authorizerdev/authorizer-js@^3.0.0-rc.1
# or
pnpm add @authorizerdev/authorizer-js@^3.0.0-rc.1
@authorizerdev/authorizer-react (v2)
- Version: use authorizer-react
2.0.0-rc.1(or compatible v2) with authorizer-js v3. - Breaking: build system (tsdx to tsup), output paths (for example
dist/index.cjs,dist/index.mjs), and Node.js >= 18. - Types: same renames as authorizer-js (for example
SignUpRequest,LoginRequest).
Example migration for type imports:
// Old
import { SignupInput, LoginInput } from '@authorizerdev/authorizer-js'
// New
import { SignUpRequest, LoginRequest } from '@authorizerdev/authorizer-js'
Other libraries
- authorizer-vue, authorizer-svelte, authorizer-go, authorizer-flutter-sdk, and other repos under the Authorizer org will be updated for v2 compatibility; use versions that explicitly support Authorizer server v2 when available.
7. Migration Checklist
- Copy all existing v1 credentials either from the dashboard or using the
_envGraphQL query with your admin secret (see Step 1 above). Do this before shutting down v1. - Replace all v1 env / dashboard config with CLI arguments at server start.
- Set
--client-idand--client-secret(required). - Set
--admin-secretand JWT options (--jwt-typeand--jwt-secretor key pair) at startup. - Stop calling
_update_env,_admin_signup, and_generate_jwt_keys; remove or replace with startup config. - Update Docker/K8s/deployment to pass config as CLI args (or via a wrapper that maps env to args).
- Upgrade @authorizerdev/authorizer-js to v3 and @authorizerdev/authorizer-react to v2; update type names and Node version as needed.
- Use kebab-case flags (for example
--database-url) and avoid deprecated names (database_url,env_file, etc.). - Re-test admin login, JWT issuance, and any flows that previously depended on dashboard-updated env.
Authorization (FGA)
v2 adds an embedded OpenFGA engine for relationship-based access control (ReBAC). You author an authorization model (OpenFGA DSL: types + relations), grant access with relationship tuples (for example user:<id> is viewer of document:1), and have your apps check access with check_permissions.
What's new
- Embedded OpenFGA engine. Enabled by default when the main database is SQL (SQLite, Postgres, MySQL), reusing that same database. For NoSQL main databases (MongoDB, DynamoDB, …) it is off unless you set
--fga-store(sqlite/postgres/mysql/memory) and--fga-store-url. - Client query operations:
check_permissionsandlist_permissions(the subject defaults to the calling principal; an explicituseris honored only for super-admins or self). - Admin GraphQL operations (super-admin,
_fga_prefix):_fga_write_model,_fga_get_model,_fga_write_tuples,_fga_delete_tuples,_fga_read_tuples,_fga_list_users,_fga_expand, and_fga_reset. Dashboard UI under Authorization → Step 1 Define model / Step 2 Grant access / Step 3 Test access.
Adoption checklist
- Define the authorization model first via the dashboard (Authorization → Step 1 Define model) or the
_fga_write_modeladmin mutation. - Grant access with tuples using
_fga_write_tuples(dashboard Step 2 Grant access) to relate subjects to objects. - Adopt
check_permissionsincrementally by adding access checks to one call site at a time (uselist_permissionswhere it fits).
Full reference: Authorization (FGA).