identity & security

A Comprehensive Guide to Auth0 Security Against Identity Attacks

A deep-dive guide for developers on essential Auth0 security best practices. Learn to prevent misconfigurations, account fraud, MFA bypass, and token hijacking.

In this extensive guide, we collected a number of security recommendations to fortify an Auth0 tenant addressing the current identity threat landscape that Auth0 customers (and others) are facing on a daily basis.

Five Focus Areas

There are five areas that are prevailing in the identity space and need to be addressed by Auth0 customers nowadays. These are:

  1. Misconfigurations
  2. Account creation fraud
  3. Attacks targeting credentials
  4. MFA bypass
  5. Session and token hijacking.
There are five areas that are prevailing in the identity space and need to be addressed by Auth0 customers nowadays

In the era of AI, these vectors, most of the time, are high-velocity automated attacks powered by bots and botnets, residential proxies, and phishing infrastructures as a service.

As a main principle, from the identity defense standpoint, we encourage our customers to apply two efficient practices:

  1. Apply the right configurations and best practices coming from the identity industry and Auth0 specific recommendations
  2. Get your detection guards up to be able to respond when your security posture is changing, for example, due to weakened configurations or when new attack vectors are emerging.
TAs a main principle, from the identity defense standpoint, we encourage our customers to apply two efficient practices

We have extensively covered the detection component in a series of articles on signup fraud, refresh token security, and credential stuffing. These detections are available publicly as Auth0 Detection Catalog itself, a collection of detection rules for security monitoring of Auth0 environments. In this guide, we will mostly focus on configurations, best practices, and responses.

Misconfigurations: The First Line of Auth0 Defense

Misconfiguration leading to security risks applies both to the tenant/identity provider and application levels. Inappropriate session management, token validation, and token storage are all examples of application-side misconfiguration. Leveraging known open-source libraries and SDKs, for example, for token verification and handling identity protocol specifics can significantly reduce this risk. We focus mainly on the tenant-side misconfigurations in this guide.

The best way to get a quick snapshot of your current security-related configurations is to run Checkmate for Auth0 that reviews your tenant configurations against standards, guidance, and best Auth0 security practices. Checkmate is a self-service tool. The Readiness Check feature is also available directly in the Auth0 Dashboard. At the time of writing, Checkmate offers a more extensive set of configuration review validators. In this section, we will highlight a few common recommendations.

The Authorization Code grant with Proof Key for Code Exchange (PKCE) should be your default choice of OAuth2.1 grants, even for regular web applications, to mitigate the risk of authorization code injection attacks. This is an application-side configuration, however, it can be enforced on the Auth0 tenant with a post-login trigger enforcing PKCE in an Auth0 Action.

exports.onExecutePostLogin = async (event, api) => {
  //enforce PKCE - if code_challenge is not defined then revoke
  if (!event.request.query.code_challenge) {  
    api.session.revoke("PKCE is enforced");
  }
};

The Implicit Grant flow is deprecated and should be avoided in favor of Authorization Code Flow with PKCE. If there is a rare edge case where the usage of the implicit grant is absolutely unavoidable, for example, when it is needed to serve legacy applications, consider using a non-default version of it. For example, a token can be delivered in the body of a response by enforcing the form_post as a response_mode that mitigates token leakage risks via a browser history or proxy logging. This setting can be enforced with an Auth0 post-login Action as well.

exports.onExecutePostLogin = async (event, api) => {
  const responseType = event.request.query.response_type;
  // When response_mode is not set in the request, 
  // it defaults to the url fragment
  const responseMode = event.request.query.response_mode || 'url';

  // Implicit Grant without form_post is not allowed
  if((responseType && (responseType.includes('token') ||
       responseType.includes('id_token'))) && 
       (!responseMode || responseMode !== 'form_post')){
          api.session.revoke('Login is not allowed. Contact support.');
          console.log(`Application ${event.client.client_id} applies insecure grant:
                       response_type=${responseType} and 
                       response_mode=${responseMode}`);
       }
};

Another grant that Checkmate recommends to avoid is the Resource Owner Password Grant (ROPG) where direct user credentials are provided in the request to the /token endpoint. The major security risk is the client is absolutely trusted with user credentials, but if your application operates in the user environment, that is a public client, this assumption cannot be held. Even if the application developer promises to handle the credentials securely, a client-side vulnerability (like cross-site scripting or malicious third-party code) could compromise the entire login attempt, making user credentials vulnerable to direct theft. The core principle of modern identity security is to never trust the client with user credentials; they should only ever be submitted to the trusted Authorization Server (in our case, Auth0 Universal Login).

There are several reasons why developers may be tempted to use ROPG. We encourage you to consider a different solution as listed below rather than defaulting to an insecure option.

Challenge Solution that is alternative to ROPG
To avoid a web redirect Explore Auth0 Native Login
To have a full control of UI Explore Auth0 Advanced Customization for Universal Login
To implement a kiosk use case Explore OAuth2.0 device flow

When a client application needs to authenticate itself to the Authorization Server to exchange an authorization code for tokens, the method used is critical. Client authentication applies to confidential clients that can maintain confidentiality of credentials. Typical examples of such clients include regular web applications and machine-to-machine applications, as opposed to public clients that are typically Single-Page Applications (SPAs) and Native applications.

Client authentication applies to confidential clients that can maintain confidentiality of credentials

Auth0 implements standard approaches for client authentication that can be split in two distinct categories:

  1. Shared secret sent over wire (client_secret_basic and client_secret_post)
  2. No shared secret to send (private_key_jwt and mutual TLS/mTLS)

    The second group of methods provides higher security characteristics, as both private_key_jwt and mTLS rely on asynchronous cryptography where the Authorization Server is provided only the public part of a secret (directly as a public key or as a X.509 certificate). These methods inherently mitigate the risk of the client application exposing its credentials, as the sensitive private key is typically managed and secured within the application's environment, never transmitted, unlike a simple, reusable client secret string.

Attack Protection: Common Denominator to Address Account Creation Fraud and Attacks Targeting Credentials

An important layer of defense provided by Auth0 is a set of Attack Protection features led by Auth0 Bot Detection. We would like to emphasize that each of these defenses can be customized by, for example, adjusting various thresholds, changing sensitivity, or choosing responses. These customizations help to maintain a proper friction that is specific to the normality of your particular environment and adequate to the current threat landscape that you are experiencing. For example, in case of Auth0 Suspicious IP throttling, it is important to set thresholds for failed logins and signups counts and refill rates for both adequately for your consumption. They should not be over permissive allowing an attack to stay undetected or too conservative to affect legitimate consumption. We recommend reconsidering defaults at the time of deploying/enabling these features in the tenant.

Yet another powerful capability often underutilized by customers is adaptability of these features to fit the current threat landscape. Even better, in combination with detections that we published and the respective Auth0 Management API, you can implement powerful security automation, tightening controls when under attack and relaxing when otherwise.

For example, when credential stuffing signals assert an ongoing attack, tighten Bot Detection configurations with the help of the Auth0 Management API, for example, increase sensitivity when When Risky mode is preferred or change configuration to Always when the attack is especially persistent.

Credential stuffing signals assert an ongoing attack

When analysing results of signals observed by Attack Protection features, one result of monitoring can be the identification of strong signals of malicious activities, for example, IPs, geo-locations, and TLS fingerprints. This information can be further leveraged by Network ACL blocking. As an example, we published the detection logins_or_signups_from_suspicious_ips.yml in the Auth0 Detection Catalog.

Account Creation Fraud

Fraudulent signups, quite often, are followed by direct financial fraud like manipulation of credit card information, consumption/collection of sign-up incentives, or enrolling into SMS MFA for executing toll fraud.

In most of the cases, email is the main connection and communication channel between your business and the user. We also can utilize it for diagnosing suspicious behavior. We focus on verification of an email and reputation of this email before allowing users to register in our application.

Auth0 has out of box capabilities for email verification via email magic link and email OTP.
The main difference between these two approaches is that when email OTP is used, the user is not created in your tenant and thus no bypass of email verification requirement is possible. While when the email magic link-based verification is used, the user account created in the tenant with the email_verified attribute set to false. Therefore, it is important to apply additional controls and deny access for users with unverified emails within a post-login Action.

exports.onExecutePostLogin = async (event, api) => {
  if(!event.user.email_verified){
    api.session.revoke("You cannot login without verified your email");
  }
};

Note that we revoked the whole session. This will deny access and revoke refresh tokens as well. Auth0 gives the flexibility to deny access without terminating with api.access.deny a session and/or revoking refresh tokens. However, for better security characteristics default to complete session termination that will revoke bound refresh tokens by default. .

One of strong indicators of ongoing attack is the usage of email domains with low reputation (note: this resource is updated every month by the author), that are, for example, disposable domains for easy fraudulent access. To respond to such threats, you can disallow usage of known disposable domains for your applications with a post-login Action.

exports.onExecutePreUserRegistration = async (event, api) => {
  const DENY_DOMAIN_LIST = ["drrieca.com", "dependity.com"];
  // Extract the domain
  const emailDomain = event.user.email?.split('@').pop();

  if (DENY_DOMAIN_LIST.includes(emailDomain)) {
    api.access.deny('Registration denied. Contact our support center.');
    console.log(`Registration denied for user ${event.user.email}. 
                  Domain '${emailDomain}' is on the deny list.`);
  }
};

As an alternative, email reputation based services can be utilized, for example, Twilio SendGrid reputation service.

When simply avoiding low reputation domains is not an option from a business perspective, you can flag these users and apply special treatment for them. Some possible special handling procedures are:

  • disallow enrolments to SMS-based MFA to avoid the toll fraud attack
  • minimize session lifetime and inactivity timer with session APIs in Actions
  • increase assurance in these identities through, for example, identity proofing or stronger MFA factors.

Below is an example of a post-login Action leveraging the Auth0 custom MFA capability to reroute a user to a non-SMS MFA factor when that user has signed up with a low reputation email domain.

exports.onExecutePostLogin = async (event, api) => {
    // Check if the user is enrolled in any factor other than 'email'.
    const hasNonEmailFactor 
          = event.user.enrolledFactors?.some(factor => factor.type !== 'email');
    let isDisposable = event.user.app_metadata?.isDisposable ?? null;

   // This logic can be a part of a separate Action 
   // Setting the isDisposable flag after registration
    if (event.stats.logins_count === 1 || isDisposable === null) {
        try {
            isDisposable = isLowReputationDomain(event.user.email);
        } catch (e) {
            // Fail secure
            // If the external API fails for any reason,
            // assume the domain is disposable to protect the account
            console.error("External domain reputation check failed." + 
                           "Assuming disposable.", e);
            isDisposable = true; 
        }
        // Persist as app_metadata
        api.user.setAppMetadata("isDisposable", isDisposable);
    }

    // Define the factors once, based on the disposable email status.
    const nonSmsFactors = [{ type: 'push-notification' }, { type: 'otp' }];
    const smsFactor = [{ type: 'phone' }];
    // if disposable domain -> use non-SMS, otherwise -> use SMS
    const factorsToUse = isDisposable ? nonSmsFactors : smsFactor;

    if (hasNonEmailFactor) {
            api.authentication.challengeWithAny(factorsToUse);
    } else {
            api.authentication.enrollWithAny(factorsToUse);
    }
};

function isLowReputationDomain(email){
  var emailDomain = email.split('@')[1];
  // Alternatively, make an external check 
  var LOW_REPUTATION_DOMAIN_LIST = ["drrieca.com", "dependity.com", "atko.email"];
  return LOW_REPUTATION_DOMAIN_LIST.includes(emailDomain);
}

When toll fraud is the main incentive for a threat actor, that is sending as many SMS as possible, simple throttling logic can be an effective demotivator. Auth0 provides built-in limiting to 10 SMS per hour sent by one user over SMS MFA. However, this defense is not efficient when a threat actor creates multiple users. In this case, an application level or even a tenant level limit can be used to stop SMSs being sent. Alternatively, phone providers’ specific throttling capabilities, like Twilio, can be leveraged as well. This logic can be implemented within a dedicated send-phone-message trigger available for Actions that we exemplify below.

const TIME_WINDOW_MS = 60 * 60 * 1000; // 1 hour in milliseconds

exports.onExecuteSendPhoneMessage = async (event, api) => {
    const ManagementClient = require('auth0').ManagementClient;
    
   // Enhance with the Token Caching technique as per 
   // https://support.auth0.com/center/s/article/Caching-tokens
    const management = new ManagementClient({
        domain: event.secrets.domain,
        clientId: event.secrets.clientId,
        clientSecret: event.secrets.clientSecret,
    });

    try {
      const now = Date.now();

      const id = event.client.client_id;
      const fetchParameters = await management.clients.get(id);
      const { client_metadata } = fetchParameters;

      // Read parameters if set or assign defaults
      const allowedSms = parseInt(client_metadata.allowed_sms_send_per_hour, 10) 
                        || 100; 
      let smsResetTimer = parseInt(client_metadata.sms_reset_timer, 10) || now;
      let currentSmsCount = parseInt(client_metadata.current_sms_count, 10) || 0;

      // Check if parameters were defined as application metadata
      // or defaults were selected
      if(!client_metadata.allowed_sms_send_per_hour ||
         !client_metadata.sms_reset_timer ||
         !client_metadata.current_sms_count) 
        console.log('Some or all parameters are not defined.' +
                      'Inititilizing with a default values ' +
                      '(max count, current count, reset timer): ',
                       allowedSms, smsResetTimer, currentSmsCount);


     if ((now - smsResetTimer) < TIME_WINDOW_MS){
      // Still within a current time window
        if (currentSmsCount >= allowedSms) {
          throw Error("no more sms to send within this hour");
        }else {
   // Still in a time window.
   // Send SMS and increase the counter

          await sendPhoneMessage();
          currentSmsCount++;
        }
     }else{
        // Time window expired.
      // Resetting the counter to 1 and timer to now.'
        // As well as sending a SMS.

        await sendPhoneMessage();
        currentSmsCount = 1;
        smsResetTimer = now;
     }

    const params =  { 
      client_metadata: {
        "sms_reset_timer": smsResetTimer.toString(),
        "current_sms_count": currentSmsCount.toString(),
     "allowed_sms_send_per_hour": allowedSms.toString()
    }};

      const updateResult = await management.clients.update(id, params);
    } catch (e) {
      console.log(e)
      // Handle error
    }
};

async function sendPhoneMessage(){
  //your provider logic
}

In the exemplifying Action above, we stored counters and a reset timer as application metadata, thereafter, we retrieve and update these parameters with Management API. While this allows us to contain the logic purely within an Auth0 tenant, you need to be aware about the consumed rate limits. Alternatively, the Action can call for external APIs for operating with counters and timers. Additionally, this measure, that is the throttling send-phone-message Action, can be applied temporarily when an attack is identified by the published detection sms_bombarding.yml, preferably with automation outlined in the section previous section.

Another approach for throttling is based on geography, that is when you notice that you get suspicious traffic outside of the countries your users normally operate. Below is an example of a send-phone-message Action that throttles based on geography.

const PhoneNumber = require('libphonenumber-js');
exports.onExecuteSendPhoneMessage = async (event, api) => {
  //List of disallowed countries by code
  const DISALLOWED_COUNTRY_CODES = ["MX", "EG"];

  const to = event.message_options.recipient;
  const code = event.message_options.code;

  var pn = new PhoneNumber(to);
  const phoneCountryCode = pn.country;

  if(!DISALLOWED_COUNTRY_CODES.includes(phoneCountryCode)){
    sendPhoneMessage(phoneCountryCode, to, code);
  }else{
    // Prevent sending SMS to disallowed countries
    var errorMessage = `An attempt to enroll with phone number ${to} 
                        from country ${phoneCountryCode}`;
    throw new Error(errorMessage)
  }
};

async function sendPhoneMessage(countryCode, to, code){
  //your provider logic here
}

Attack Targeting Credentials

The main risk of an attack targeting credentials is an account takeover leading to direct financial loss by consumers. We published 8 Log Detections for Credential Stuffing and MFA Exploit Prevention as a starting point for you to be able to quickly identify the presence of such an attack in your environment. Auth0 Attack Protection features are all contributing to mitigation of this risk as discussed above. There are additional aspects to consider, that we discuss in this section.

Password reset flow

Of course, when passwords are used as a primary authentication method strong password policies contribute a lot into overall security. Such a policy is built on password strength, password history, password dictionary, and disallowing usage of personal data.

Password reset flow

Another component of an effective password policy, that is often overlooked, is the password reset flow. Moreover, a strong password reset flow can be your strong defense against such attack vectors when the email inbox is in the control of a threat actor.

The weakness of a default password reset flow is that you are delegating 100% of the responsibility to an email box, and thus an email becomes the weakest link. What if the email inbox has been compromised?

To address this risk, Auth0 offers a powerful combination of a password reset flow trigger and custom MFA. These two features can be used to build the following defenses:

  • Allow password reset only when the email is validated
  • Require a MFA challenge when at least one factor is enrolled, and require identity proofing when no factors are enrolled
  • Require identity proofing when you transaction is risky or if you suspect the email compromise
  • Redirect to a custom MFA provider if the customer has specific needs, for example, use a knowledge-based approach as a supportive measure to increase assurance in the transaction.

Effective password policies fall short when credentials are exposed as a result of relying on insecure practices at the application level such as ROPG and cross-origin authentication leaking user credentials directly via the application level. We already discussed alternatives to these insecure practices when covering misconfigurations.

Onboarding passkeys

The number one mitigation of attacks targeting credentials is the minimization of credentials by onboarding passkeys. The second-best step is backing up passwords with MFA, preferably a strong one such as WebAuthn. Be aware that introducing SMS-based MFA brings other issues related to toll fraud.

The extensive post All You Need To Know About Passkeys at Auth0! and the Passkey Handbook by Philippe De Ryck provides a great introduction into understanding the technology of passkeys.

Auth0 enhanced passkeys user experience reduces the friction to adopt it in several ways:

  • native passkeys for mobile applications
  • remote authentication enforced with proximity and local enrollment
  • progressive enrollment.

Enabling the passkey in Auth0 does not remove the possibility to also provide password-based authentication as both authentication methods are available within one Database connection.

Username-Password-Authentication Screen

While still offering passwords as an option, we need to outline a migration path. There are at least the following two approaches that can be implemented with Auth0.

Approach 1: Start offering passkeys to existing password-based users by enabling the Progressive enrollment for passkeys option. Once a user is enrolled into a passkey, you can disallow usage of a password for this user through a post-login Action. This will allow gradually phasing out passwords for those users who have enrolled into passkey.

Approach 2: In the situation of green field deployment, that is, you want to offer only passkeys (well done!) or after running Approach 1 for some time has led to the substantial adoption of passkeys, you can disallow passwords for authentication all at once with some custom logic implemented with a post-login Action.

We exemplify both approaches below:

exports.onExecutePostLogin = async (event, api) => {
  // Optional flags to combine two approaches
  const denyPwdForAll = false;
  const denyPwdPerUser = true;

  const nameOfPasskeyEnabledConnection = 'Username-Password-Authentication';
  // Get a method used for authentication
  const methods = event.authentication?.methods;
  const ifPwdUsed = methods?.some(method => method.name === 'pwd')

  // Approach 1: deny for the whole connection
  if (denyPwdForAll && 
      event.connection.name == nameOfPasskeyEnabledConnection){
    if(ifPwdUsed){
      api.session.revoke('Please, login with passkeys');
    }
  }

  // Approach 2: deny per user basis
  if (denyPwdPerUser && ifPwdUsed){
    const ManagementClient = require('auth0').ManagementClient;

    // Enhance with Token Caching technique as per 
    // https://support.auth0.com/center/s/article/Caching-tokens
    const management = new ManagementClient({
        domain: event.secrets.domain,
        clientId: event.secrets.clientId,
        clientSecret: event.secrets.clientSecret,
    });

    const params =  { include_totals: true};
    const id = event.user.user_id;

    try {
      const res = await management.users.authenticationMethods.list(id, params)
      if (res.response.total > 0){ 
          const authenticators = res.response?.authenticators
          
          const ifPasskeyPresent = authenticators?.some(authenticator => 
                authenticator.type === 'passkey');
          // If a user enrolled into a passkey, deny login with a password
          if(ifPasskeyPresent){
            api.session.revoke('Please, login with passkeys');
          }
      }
    } catch (e) {
      console.log(e)
      // Handle error
    }
  }
};

Note that as the exemplifying Action uses Management API, it contributes to the overall rate limit count.

MFA Bypass

SIM swapping, push notifications fatigue, and OTP phishing — these all go away once phishing-resistant factors have been onboarded. So start adopting MFA today!

A substantial challenge that the customer identity domain faces is the logical barrier of introducing extra friction to users. To address this, Auth0 supports two paradigms for MFA implementation:

  1. Step-up authentication protects the resource based on the idea that applications can be split into sensitive and not sensitive areas. Prompt for a MFA challenge only when the user navigates to a more sensitive part of your application.
  2. Adaptive MFA protects the session. Based on a built-in risk assessment score — the user will be prompted only when the risk is high based on such signals like device, travel velocity, and IP reputation. It supports bringing your own risk assessment where developers can integrate their own risk assessment logic using Actions, thereby extending the platform's native capabilities to fit unique business needs and emerging threats.

In 8 Log Detections for Credential Stuffing and MFA Exploit Prevention, there are several detections to identify some forms of MFA bypass, for example, push notifications fatigue and sms pumping attacks.

Session and Token Hijacking

Session and token hijacking can lead to impersonation that gives opportunity to unauthorized access and account take over. Even passwordless defenses fall short when an attacker can obtain access by stealing a session, represented as browser cookies, or token issued by the OAuth2.1 grants. Main attack vectors here remain to be client-based vulnerabilities, for example, cross-site scripting (XSS) or malware; adversary-in-the-middle phishing, for example, leveraging Evilginx or Muraena; and leaking tokens through insecure application level practices, for example, as a result of tokens delivered via browser url when the implicit grant is used.

There are three primary types of tokens used in modern authentication flows: ID tokens, access tokens, and refresh tokens. Briefly, an ID token is used to identify a user; an access token gives short lived access to a resource/API; a refresh token is a long lived artifact used to renew access and/or access tokens without user interactions. These tokens are minted by the Authorization Server as a result of successful execution of an OAuth 2.1/OIDC flow. A user session represents a long lived presence of a user within an application or service, and created as a result of interactions of a user with a server. It is tracked as a cookie when the user agent is capable of storing cookies, which is the case for a browser.

Note that both refresh tokens and user sessions provide a long lived authentication state and access for a user. However, a user session and a refresh token are not the same. Refresh token will give you back a specific access token and optionally an ID token. With an active session you can request as many different tokens (with different scope, audiences) as you want. We need to protect all of them for better security.

Token hijacking

On a token front, the number one defense is to follow OAuth2.1 best practices, such as, default to the Authorization Code grant with PCKE to avoid leakage of ID and access tokens and apply refresh token rotation to protect respective refresh tokens. In Refresh Token Security: Detecting Hijacking and Misuse with Auth0, we discuss possible ways to detect refresh token hijacking and their possible responses.

Once a token or session is successfully hijacked, the threat actor begins impersonating the user, remaining undetected for as long as the artifact's lifetime allows. Moreover, a threat actor may choose to store them for the time being to exploit later or to sell these sessions or tokens in cybercrime markets. The only effective defense against it is the aggressive control of lifetime and inactivity timers for all identity artifacts.

Auth0 provides granular controls to define separate maximum and idle timers for the ID token, access token, and refresh token per application (ID token and refresh token) and API (access token). By minimizing these timers, you dramatically reduce the window of opportunity for an attacker to use a stolen artifact, effectively turning the clock into your ally against impersonation.

ID and Refresh Token OptionsAccess Token Expiration Option

A strong response to mitigate the risk of token hijacking in the first place is sender-constrained flows where a resource has means to validate if a received access token is allowed to be used by the sender (that is, the application). Auth0 provides two approaches for sender-constraining to choose from — acting at transport and application layers that are mutual-TLS and Demonstrating Proof-of-Possession (DPoP) respectively. The recent article Protect Your Access Tokens with DPoP (Demonstrating Proof of Possession) compares these two approaches from the application developer standpoint and gives a great starting point.

Session hijacking

Before we proceed with responses for the session hijacking, it is important to understand that there are more than just one session involved:

  • Application session: is entirely under your control.
  • Auth0 session: is under Auth0 control, controlled with Session Management APIs.
  • Identity Provider (IdP) session: optional when an external IdP is involved.

While an application session is under the control of developers, it is critical to terminate application sessions once the respective Auth0 session has been revoked by leveraging Back Channel Logout.

Although all three sessions should be handled holistically, we focus on the Auth0 session in the rest of this guide.

There are three critical settings that you need to control when it comes to protecting session cookies: persistence, lifetime, and inactivity timer. While all three parameters can be configured on the tenant level through Auth0 Dashboard or Auth0 Management API, the most flexibility and granularity is given by the dynamic control through api.session available in post-login Actions. This dynamic control can be leveraged as a more plausible way (that is, alternative to stopping the consumption at all) to mitigate the risk when observing such suspicious signals as login from high-risk countries, from suspicious TLS fingerprints, when IP attribution changes throughout a single session, etc. We provide an example of such a post-login Action:

exports.onExecutePostLogin = async (event, api) => {
  const sessionInitialASN = event.session?.device?.initial_asn;
  const sessionCurrentASN = event.request.asn;

  const created = Date.parse(event.session?.created_at ?? "");
  // Read timers defined in the application metadata
  const lifetimeWhenRisky = event.client.metadata.session_lifetime_when_risky;
  const inactivityWhenRisky = event.client.metadata.session_inactivity_when_risky;

  // if there the ASN change
  if (sessionInitialASN &&
      sessionCurrentASN &&
      sessionInitialASN != sessionCurrentASN) {
        if (event.user.app_metadata.isAdmin){
          // Be strict for Admins - simply revoke the session
          api.session.revoke('Invalid ASN change')
        }else {
          // Otherwise minimize session duration and disable persistence
          api.session.setCookieMode('non-persistent');
          if (event.session?.id && 
              lifetimeWhenRisky && 
              inactivityWhenRisky) {
              api.session.setExpiresAt(created + Number(lifetimeWhenRisky));
              api.session.setIdleExpiresAt(created + Number(inactivityWhenRisky));
          }
        }
  }
};

Once you have detected a potential account takeover, you need to respond promptly to contain the damage. In the identity space, you do that by terminating the user session, revoking tokens, blocking the user, and taking the user through password reset flow. These effective four steps are powered by a combination of Auth0 Management API, Auth0 Authentication API calls and the post-change-password trigger.

Step 1 Revoke tokens & Terminate session Sessions: DELETE /api/v2/users/{user_id}/sessions

Refresh tokens: DELETE /v2/users/{user_id}/refresh-tokens
Step 2 Block the user PATCH /api/v2/users/{user_id}
Body:
{
     "blocked": true
}
Step 3 Request password reset POST /dbconnections/change_password
Body:
{
     "email": "{user_id}",
     "connection": "{connection_name}"
}


Alternatively you can use a Management API call:
POST /api/v2/tickets/password-change
and send an email with the ticket elsewhere
Step 4 Unblock the user Post Change Password Action

Below is an example of such a post-change-password Action unblocking the user as Step 4:

exports.onExecutePostChangePassword = async (event, api) => {
  const ManagementClient = require('auth0').ManagementClient;

  //apply Token Caching as per
  //https://support.auth0.com/center/s/article/Caching-tokens
  const management = new ManagementClient({
      domain: event.secrets.domain,
      clientId: event.secrets.clientId,
      clientSecret: event.secrets.clientSecret,
  });

  const params =  { id : event.user.user_id};
  const data = { "blocked" : false};

  try {
    const res = await management.users.update(params, data)
  } catch (e) {
    console.log(e)
    // Handle error
  }
};

Note that as the exemplifying Action uses Management API, it contributes to the overall rate limit count.

Next Steps: It is Just the Beginning

Securing customer environments is not a destination, but a continuous journey. As the threat landscape evolves, we encourage Auth0 customers to explore new features as they come. For example, consider supplementing Akamai signals and leveraging JA3/JA4 fingerprints now available in Auth0 logs, Actions, and Network ACL.

Furthermore, explore the Auth0 detection catalog for building a rich security center of your own.

This wraps up this guide, but not your security journey!