Auth0 Configuration
Tenant
| Property | Value |
|---|---|
| Tenant | gatelithix |
| Domain | gatelithix.us.auth0.com |
| Region | US |
| Dashboard | https://manage.auth0.com/dashboard/us/gatelithix |
Applications
Gatelithix Dashboard (SPA)
| Property | Value |
|---|---|
| Client ID | I1rEXZhT4pRcHIBYXoUG3x0J2NgVuZKq |
| Type | Single Page Application |
| Auth method | None (PKCE) |
| Callback URLs | https://dashboard.paylithix.com |
| Logout URLs | https://dashboard.paylithix.com |
| Web Origins | https://dashboard.paylithix.com |
API Explorer Application (M2M)
| Property | Value |
|---|---|
| Client ID | SszFMlZa9HIhRNadnXI6deFzei3ybnGj |
| Type | Machine to Machine |
| Purpose | Auth0 Management API access, gateway server-side auth |
| Client Secret | Stored in GCP Secret Manager: auth0-client-secret (version 3) |
APIs (Resource Servers)
Gatelithix Gateway API
| Property | Value |
|---|---|
| Identifier | https://api.gatelithix.com |
| Signing Algorithm | RS256 |
| Token Lifetime | 86400 seconds (24 hours) |
| Scopes | read:payments, write:payments, admin |
How Auth Works
Dashboard Login Flow
- User visits
https://dashboard.paylithix.com - Next.js app redirects to
https://gatelithix.us.auth0.com/authorizewith SPA client ID - User authenticates (email/password via Auth0 Universal Login)
- Auth0 redirects back to
/auth/callbackwith authorization code - Admin app exchanges code for tokens (PKCE flow)
- JWT access token includes
aud: https://api.gatelithix.com - Admin app includes token in API calls to gateway
Gateway JWT Validation
- Gateway reads
AUTH0_DOMAINenv var (gatelithix.us.auth0.com) - Fetches JWKS from
https://gatelithix.us.auth0.com/.well-known/jwks.json - Validates JWT signature, audience (
https://api.gatelithix.com), and expiry - Extracts user identity from token claims
Environment Variables
Gateway (Cloud Run)
| Variable | Value | Source |
|---|---|---|
AUTH0_DOMAIN | gatelithix.us.auth0.com | Terraform env_vars |
AUTH0_AUDIENCE | https://api.gatelithix.com | Terraform env_vars |
AUTH0_CLIENT_SECRET_NAME | projects/gatelithix-core/secrets/auth0-client-secret/versions/latest | Terraform env_vars |
Dashboard (Build-time)
| Variable | Value | Source |
|---|---|---|
NEXT_PUBLIC_AUTH0_DOMAIN | gatelithix.us.auth0.com | GitHub Actions vars / Docker build arg |
NEXT_PUBLIC_AUTH0_CLIENT_ID | I1rEXZhT4pRcHIBYXoUG3x0J2NgVuZKq | GitHub Actions vars / Docker build arg |
NEXT_PUBLIC_AUTH0_AUDIENCE | https://api.gatelithix.com | GitHub Actions vars / Docker build arg |
NEXT_PUBLIC_API_URL | https://api.gatelithix.com | GitHub Actions vars / Docker build arg |
GCP Secret Manager
| Secret | Purpose | Current Version |
|---|---|---|
auth0-client-secret | M2M client secret for gateway server-side auth | v3 |
Post-Login Action: Add Roles to Tokens
A post-login Action reads app_metadata.roles and injects them into the JWT as a custom claim (https://gatelithix.com/roles). Without this Action, the dashboard navigation is empty because all nav items are gated by RoleGate.
Action name: Add roles to tokens
Trigger: post-login (v3)
Code:
exports.onExecutePostLogin = async (event, api) => {
const roles = event.user.app_metadata?.roles || [];
if (roles.length > 0) {
api.idToken.setCustomClaim("https://gatelithix.com/roles", roles);
api.accessToken.setCustomClaim("https://gatelithix.com/roles", roles);
}
};Assigning Roles to Users
Roles are stored in app_metadata.roles. To assign a role:
TOKEN=$(curl -s -X POST "https://gatelithix.us.auth0.com/oauth/token" \
-H "Content-Type: application/json" \
-d '{"client_id":"SszFMlZa9HIhRNadnXI6deFzei3ybnGj","client_secret":"'$(gcloud secrets versions access latest --secret=auth0-client-secret --project=gatelithix-core)'","audience":"https://gatelithix.us.auth0.com/api/v2/","grant_type":"client_credentials"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# Find user by email
curl -s "https://gatelithix.us.auth0.com/api/v2/users?q=email:USER@EXAMPLE.COM" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import sys,json; [print(u['user_id']) for u in json.load(sys.stdin)]"
# Assign role (URL-encode the user_id: | → %7C)
curl -s -X PATCH "https://gatelithix.us.auth0.com/api/v2/users/USER_ID_HERE" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"app_metadata":{"roles":["platform_admin"]}}'Available roles: platform_admin, platform_support, iso_admin, merchant_admin, merchant_developer, read_only_auditor
Local Development
The production Auth0 app does NOT include http://localhost URLs — adding localhost to a production OAuth app is a security risk (redirect attack vector).
For local dev, use DEV_MODE=true (make dev) which bypasses Auth0 entirely. If you need to test the real Auth0 flow locally, create a separate Auth0 application for development or temporarily add localhost via the Management API and remove it when done.
Debugging
Admin portal returns 403 after Auth0 login
Cloud Armor WAF’s SQL injection rule (sqli-v33-stable) triggers a false positive on the Auth0 OAuth callback URL because the code= and state= query parameters contain base64 strings that resemble SQL injection payloads.
The frontend-waf policy has a priority-900 allow rule that exempts OAuth callbacks (request.path == '/' && request.query.matches('code=.*&state=.*')). If this rule is missing, re-add it:
gcloud compute security-policies rules create 900 \
--security-policy=frontend-waf \
--project=gatelithix-core \
--action=allow \
--expression="request.path == '/' && request.query.matches('code=.*&state=.*')" \
--description="Allow Auth0 OAuth callback (code+state params trigger false positive on sqli-v33)"Admin portal shows “Callback URL mismatch”
Auth0 is rejecting the redirect_uri. The admin portal sends window.location.origin (e.g., https://dashboard.paylithix.com) as the redirect_uri. This bare origin MUST be in the Allowed Callback URLs list.
# Check current callback URLs via Management API
TOKEN=$(curl -s -X POST "https://gatelithix.us.auth0.com/oauth/token" \
-H "Content-Type: application/json" \
-d '{"client_id":"SszFMlZa9HIhRNadnXI6deFzei3ybnGj","client_secret":"'$(gcloud secrets versions access latest --secret=auth0-client-secret --project=gatelithix-core)'","audience":"https://gatelithix.us.auth0.com/api/v2/","grant_type":"client_credentials"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# View current callbacks
curl -s "https://gatelithix.us.auth0.com/api/v2/clients/I1rEXZhT4pRcHIBYXoUG3x0J2NgVuZKq?fields=callbacks" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
# Fix: set callbacks to match what the app sends (window.location.origin)
# Production only — no localhost (OAuth redirect attack vector)
curl -s -X PATCH "https://gatelithix.us.auth0.com/api/v2/clients/I1rEXZhT4pRcHIBYXoUG3x0J2NgVuZKq" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"callbacks":["https://dashboard.paylithix.com"],"allowed_logout_urls":["https://dashboard.paylithix.com"],"web_origins":["https://dashboard.paylithix.com"]}'Admin portal shows “authorize” DNS error
The admin portal is trying to redirect to Auth0 but the domain or client ID is wrong.
# Check what the admin image was built with
curl -s https://dashboard.paylithix.com/ | grep -o 'auth0[^"]*' | head -5
# Check GitHub Actions vars
gh variable list --repo paylithix/paylithix-gateway | grep AUTH0Gateway rejects JWT tokens
# Verify JWKS endpoint is reachable
curl -s https://gatelithix.us.auth0.com/.well-known/jwks.json | jq '.keys | length'
# Check gateway Auth0 config
gcloud run services describe api-gateway --project=gatelithix-core --region=us-central1 \
--format="value(spec.template.spec.containers[0].env)" | tr ';' '\n' | grep AUTH0
# Verify Secret Manager has real secret (not placeholder)
gcloud secrets versions access latest --secret=auth0-client-secret --project=gatelithix-core | head -c 10Auth0 Management API access
# Get a fresh Management API token (24-hour expiry)
# Go to Auth0 Dashboard → APIs → Auth0 Management API → API Explorer tab → Copy token
# Or use M2M credentials:
curl -s -X POST "https://gatelithix.us.auth0.com/oauth/token" \
-H "Content-Type: application/json" \
--data '{
"client_id": "SszFMlZa9HIhRNadnXI6deFzei3ybnGj",
"client_secret": "'$(gcloud secrets versions access latest --secret=auth0-client-secret --project=gatelithix-core)'",
"audience": "https://gatelithix.us.auth0.com/api/v2/",
"grant_type": "client_credentials"
}' | jq -r '.access_token'
# List applications
curl -s "https://gatelithix.us.auth0.com/api/v2/clients" \
-H "Authorization: Bearer $TOKEN" | jq '.[].name'
# List users
curl -s "https://gatelithix.us.auth0.com/api/v2/users" \
-H "Authorization: Bearer $TOKEN" | jq '.[].email'