Secure Auth0 with cnspec
Scan Auth0 tenant settings, applications, connections, users, roles, and attack protection against security and compliance best practices with cnspec.
An Auth0 tenant decides who reaches your applications and on what terms, so the settings that govern sessions, second factors, and OAuth grants shape the blast radius of every stolen credential. cnspec reads an Auth0 tenant through its Management API and evaluates the tenant wide session and multi-factor policy, the applications registered in the tenant and the OAuth grants and redirect URLs they carry, the identity connections and the password strength they enforce, the APIs and whether role based access control gates their scopes, and the attack protection defenses that apply across the whole tenant.
cnspec reads configuration only. It never reads tenant logs, user passwords, or application secrets: it queries the Management API's configuration endpoints, and secret values are never returned by that API.
If you're new to cnspec, start with the Quickstart. For an overview of every identity provider cnspec can scan, see the identity scanning overview.
The Auth0 resources are experimental. Field names and behavior can change in a future cnspec release.
Prerequisites
To scan Auth0 with cnspec, you must have:
- cnspec installed on your workstation
- An Auth0 tenant
- A machine to machine application authorized for the Auth0 Management API
Authenticate
cnspec authenticates as a machine to machine (M2M) application rather than as a user. In the Auth0 dashboard, go to Applications > Applications, create an application of type Machine to Machine, and authorize it for the Auth0 Management API. Grant it the read scopes covering what you want to scan, such as read:clients, read:connections, read:users, read:roles, read:actions, read:log_streams, read:resource_servers, read:client_grants, read:organizations, read:attack_protection, and read:guardian_factors.
The tenant domain is the one shown on the application's Settings tab, such as lunalectric.us.auth0.com.
cnspec shell auth0 --domain lunalectric.us.auth0.com --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRETEnvironment variables
AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET supply the connection details:
export AUTH0_DOMAIN=lunalectric.us.auth0.com
export AUTH0_CLIENT_ID=YOUR_CLIENT_ID
export AUTH0_CLIENT_SECRET=YOUR_CLIENT_SECRETWhen these are set, you can omit the matching flags from the commands below.
Connection options
| Option | Description |
|---|---|
--domain | Auth0 tenant domain, such as lunalectric.us.auth0.com |
--client-id | Client ID of the machine to machine application |
--client-secret | Client secret of the machine to machine application |
Verify with a quick Auth0 check
Confirm that cnspec can reach your tenant by opening a cnspec shell:
cnspec shell auth0 --domain lunalectric.us.auth0.com --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRETcnspec> auth0.tenant { friendlyName sessionLifetime idleSessionLifetime }
auth0.tenant: {
friendlyName: "Lunalectric"
sessionLifetime: 168
idleSessionLifetime: 72
}If cnspec connects and reports your tenant, you're ready to scan.
Scan Auth0
cnspec scan auth0 --domain lunalectric.us.auth0.com --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRETThe tenant is a single asset. There are no child assets to discover.
When a scan completes, cnspec prints a summary of all the checks it ran, grouped by policy, along with a risk score from 0 (no risk) to 100 (highest risk). Failed checks include remediation guidance to help you fix issues. To learn more about reading scan results, read Understand cnspec Results.
Mondoo doesn't yet ship an out-of-the-box Auth0 policy, so use the checks below as a starting point and create your own policies to meet your specific requirements.
Explore and test checks interactively
Open a cnspec shell to discover resources and try out checks:
cnspec shell auth0 --domain lunalectric.us.auth0.com --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRETReview the tenant session settings
sessionLifetime and idleSessionLifetime bound how long a session survives before Auth0 requires authentication again:
cnspec> auth0.tenant {
friendlyName
sessionLifetime
idleSessionLifetime
defaultAudience
defaultDirectory
allowedLogoutUrls
}Review the multi-factor authentication policy
policy decides when users are challenged, and the per factor flags decide what they may enroll:
cnspec> auth0.guardian {
policy
otpEnabled
webAuthnPlatformEnabled
webAuthnRoamingEnabled
pushEnabled
phoneEnabled
emailEnabled
recoveryCodeEnabled
}Review the tenant attack protection
Three defenses apply tenant wide, each flattened onto one resource:
cnspec> auth0.attackProtection {
bruteForceEnabled
bruteForceMode
bruteForceMaxAttempts
suspiciousIpThrottlingEnabled
breachedPasswordDetectionEnabled
breachedPasswordDetectionMethod
breachedPasswordDetectionShields
}List the applications and how they authenticate
appType and tokenEndpointAuthMethod together tell you whether an application holds a secret at all:
cnspec> auth0.clients { name appType isFirstParty tokenEndpointAuthMethod grantTypes }Review where an application may send users
callbacks, allowedOrigins, and webOrigins bound where authentication can land and which origins may call the tenant:
cnspec> auth0.clients { name callbacks allowedLogoutUrls allowedOrigins webOrigins }Review refresh token handling
A refresh token that neither rotates nor expires is a credential with no natural end:
cnspec> auth0.clients {
name
refreshTokenRotationType
refreshTokenExpirationType
refreshTokenLifetime
refreshTokenIdleLifetime
}Review the password rules each database connection enforces
passwordPolicy applies only to database connections. Social and enterprise connections leave it empty because the identity provider owns the credential:
cnspec> auth0.connections.where(strategy == "auth0") {
name
passwordPolicy
passwordDictionaryEnabled
passwordHistoryEnabled
passwordHistorySize
passwordNoPersonalInfo
mfaActive
}Review the APIs and how their tokens are signed
cnspec> auth0.resourceServers { name identifier signingAlgorithm tokenLifetime enforcePolicies allowOfflineAccess }Review the machine to machine grants
A client grant is standing authorization for one application to call one API, so its scopes are the permissions that application always holds:
cnspec> auth0.clientGrants { audience scopes allowAllScopes client { name appType } }Review the actions running inside the authentication flow
An action runs custom code on every login it's bound to, so an undeployed or failed action and an unreviewed one are different kinds of problem:
cnspec> auth0.actions { name runtime status deployed supportedTriggers }Review where tenant events are shipped
cnspec> auth0.logStreams { name type status sink }Walk from a resource to its neighbors
Applications, connections, users, and roles reference each other, so a query can start from any of them and walk in either direction:
cnspec> auth0.clients { name enabledConnections { name strategy } }
cnspec> auth0.connections { name enabledClients { name appType } }
cnspec> auth0.roles { name users { email } }
cnspec> auth0.users { email roles { name } }Example security checks
Ensure the tenant requires multi-factor authentication
A policy of never means no login is ever challenged for a second factor:
cnspec> auth0.guardian.policy != "never"
[ok] value: trueEnsure a phishing resistant second factor is available
SMS and email codes can be relayed to an attacker. WebAuthn factors are bound to the origin, so they can't be:
cnspec> auth0.guardian.webAuthnRoamingEnabled == true || auth0.guardian.webAuthnPlatformEnabled == true
[ok] value: trueEnsure brute-force protection is enabled
cnspec> auth0.attackProtection.bruteForceEnabled == true
[ok] value: trueEnsure suspicious IP throttling is enabled
Brute-force lockout counts attempts per account. Throttling catches the opposite shape of attack, a few addresses trying many accounts:
cnspec> auth0.attackProtection.suspiciousIpThrottlingEnabled == true
[ok] value: trueEnsure breached password detection blocks the login
Detection that only notifies still lets the login through:
cnspec> auth0.attackProtection.breachedPasswordDetectionEnabled == true && auth0.attackProtection.breachedPasswordDetectionShields.contains("block")
[ok] value: trueEnsure sessions expire within a week
cnspec> auth0.tenant.sessionLifetime <= 168
[ok] value: trueEnsure idle sessions expire sooner than active ones
cnspec> auth0.tenant.idleSessionLifetime < auth0.tenant.sessionLifetime
[ok] value: trueEnsure no application uses the implicit grant
The implicit grant returns tokens in the URL fragment, where they reach browser history and referrer headers:
cnspec> auth0.clients.none(grantTypes.contains("implicit"))
[ok] value: trueEnsure refresh tokens rotate
cnspec> auth0.clients.where(grantTypes.contains("refresh_token")).all(refreshTokenRotationType == "rotating")
[ok] value: trueEnsure no refresh token lives forever
cnspec> auth0.clients.none(refreshTokenExpirationType == "non-expiring")
[ok] value: trueEnsure database connections enforce a strong password policy
cnspec> auth0.connections.where(strategy == "auth0").all(passwordPolicy == "excellent")
[ok] value: trueEnsure database connections reject breached and reused passwords
cnspec> auth0.connections.where(strategy == "auth0").all(passwordDictionaryEnabled == true && passwordHistoryEnabled == true)
[ok] value: trueEnsure your APIs enforce role-based access control
Without enforcePolicies, a token carries whatever scopes the application asked for rather than what the user's roles allow:
cnspec> auth0.resourceServers.where(isSystem == false).all(enforcePolicies == true)
[ok] value: trueEnsure access tokens are signed with an asymmetric key
HS256 signs with a shared secret, so every party that validates a token could also mint one:
cnspec> auth0.resourceServers.where(isSystem == false).all(signingAlgorithm == "RS256")
[ok] value: trueEnsure no machine to machine grant takes every scope
allowAllScopes grants each scope the API defines, including the ones added after the grant was created:
cnspec> auth0.clientGrants.none(allowAllScopes == true)
[ok] value: trueEnsure tenant events reach a log stream
Without an active stream, no record of authentication events leaves the tenant:
cnspec> auth0.logStreams.where(status == "active").length > 0
[ok] value: trueEnsure every action in the login path is deployed
An action that failed to build isn't running, so the control it implements isn't either:
cnspec> auth0.actions.all(deployed == true)
[ok] value: trueEnsure no user is left blocked indefinitely
A blocked account is an unresolved incident or an offboarding nobody finished:
cnspec> auth0.users.none(blocked == true)
[ok] value: trueReview accounts that have never signed in
cnspec> auth0.users.where(loginsCount == 0) { email createdAt }Learn more
- Auth0 Resource Pack Reference: every Auth0 resource and field cnspec can query
- Write Effective MQL: guide to authoring checks and queries