We continue our series of blog posts covering detection opportunities for Auth0 Logs streamed to your Security Information and Event Management (SIEM) tooling. Auth0 Log Streaming gives you real-time visibility and the flexibility to build custom Auth0 security monitoring for detecting recurring threats like credential stuffing. Previous posts in the series covered signup fraud and refresh token security:
- Detecting Signup Fraud: 3 Ways to Use Auth0 Logs to Protect Your Business
- Refresh Token Security: Detecting Hijacking and Misuse with Auth0
While Auth0 Security Center already offers powerful built-in monitoring for attacks like credential stuffing, multi-factor authentication (MFA) bypass, and signup attacks, the Auth0 Detection Catalog, a collection of detection rules for security monitoring of Auth0 environments, offers a complementary approach. This "Bring Your Own Detections" model gives you the flexibility and transparency to tailor detections to your unique environment and specific needs.
In this article, we cover eight powerful detection rules from the Auth0 Detection Catalog to empower your security practices. These eight security detections are designed to monitor such attack vectors as credential stuffing, MFA exploits, and authorization request abuse.
The one-two punch against credential stuffing
Credential stuffing is a persistent threat, relying on leaked credentials from third-party breaches. It often involves sophisticated botnets that try to hide their activity. The Auth0 Detection Catalog offers two detections that work together to identify and confirm these attacks.
Diagnosing credential stuffing signals
The credential_stuffing_signals.yml detection, focuses on volume-based signals, i.e. abnormal bursts — the classic signs of a bot at work.
This rule is designed to diagnose an ongoing credential stuffing attack by analyzing patterns of high-volume failed login events (f, fu, fp, fcoa) and specific Auth0 attack protection events (such as pwd_leak, limit_wc, limit_sul, limit_mu).
The provided Splunk query includes three distinct detection signals over a one-hour window by default (the window can be adjusted):
- High volume of failed events originating from a single IP address.
- A significant surge in total failed login or attack protection events across your tenant.
- An anomalous spike in the number of unique source IP addresses, which often points to a large, distributed botnet jumping through residential IP addresses.
The query also calculates normality for effective comparison and collects JA3/JA4 TLS fingerprint data. This data is crucial for clustering related attack traffic, a powerful step in subsequent investigation. We will demonstrate its use in the next detection.
index=auth0 data.tenant_name="{your-tenant-name}" data.type IN (f, fu, fp, pwd_leak, limit_wc, limit_sul, limit_mu, fcoa) ``` Signal 1 - any single IP triggers excessive number of failed/attack protection events ``` | bin _time span=1h | stats values(data.security_context.ja4) as ja4 count as cnt_events_per_ip by _time data.ip | where cnt_events_per_ip > {threshold_events_per_ip} ``` Normality for Signal 1 - average number of successful logins per IP (per hour) ``` | join [ search index=auth0 data.type = s | stats count AS successful_logins_per_ip by data.ip | stats avg(successful_logins_per_ip) AS avg_successful_logins_per_ip ] ``` Signal 2 - surge in failed login or attack protection events ``` ``` | stats values(data.security_context.ja4) as ja4 count as cnt_total_per_event by _time data.type | where cnt_total_per_event > {threshold_count_per_event}``` ``` Normality for Signal 2 - average number successful logins (per hour) ``` ``` | join [ search index=auth0 data.type = s | stats count AS successful_logins_per_s by data.type | stats avg(successful_logins_per_s) AS avg_successful_logins_per_s ]``` ``` Signal 3 - surge of observed IPs with failed events ``` ``` | stats dc(data.ip) as cnt_total_ips values(data.ip) as ip values(data.security_context.ja4) as ja4 by _time | where cnt_total_ips > {threshold_total_ips}``` ``` Normality for Signal 3 - average number of IPs with successful logins (per hour) ``` ```| join [ search index=auth0 data.type = s | stats dc(data.ip) AS successful_logins_ips by _time | stats avg(successful_logins_ips) AS avg_successful_logins_ips ]```
Logins from suspicious TLS fingerprints
The second detection, logins_from_suspicious_tls_fingerprints.yml, is built on top of credential_stuffing_signals.yml to confirm potentially compromised accounts.
This detection is designed to identify potential account compromise by correlating a successful login with JA4 fingerprints that were recently seen in the suspicious, high-volume failed attempts identified in credential_stuffing_signals.yml.
A JA4 fingerprint is composed from the TLS ClientHello message and is an alternative for security operation teams to track a user application instead of relying on the User-Agent string that can be easily manipulated and spoofed.
The detection has two steps:
- It first identifies suspicious JA4 fingerprints by looking at IPs that generated a high number of failed login or attack protection events. This step can be replaced by any of the other signals suggested in credential_stuffing_signals.yml.
- The detection then filters successful login events (s and scoa), retaining only those that share a JA4 fingerprint with the previously identified suspicious traffic.
The final output is a list of the successful login, the suspicious JA4 fingerprints, and the usernames. This provides a strong indicator that an account was successfully breached using the exact same automated tool or infrastructure responsible for the flood of failed attempts.
index=auth0 data.tenant_name="{your-tenant-name}" | bin _time span=1h ``` Successful login events and associated JA4 ``` | search data.type IN (s, scoa) | rename data.security_context.ja4 as seen_ja4_hash | fields _time, data.type, seen_ja4_hash, data.user_name ``` Filter these events using the list of JA4 hashes seen in failed events pointing to credential stuffing ``` | join type=inner seen_ja4_hash [ search index=auth0 data.type IN (f, fu, fp, pwd_leak, limit_wc, limit_sul, limit_mu, fcoa) | stats count as cnt_events_per_ip by data.security_context.ja4 data.ip | where cnt_events_per_ip > {threshold_events_per_ip} | fields data.security_context.ja4 | rename data.security_context.ja4 as seen_ja4_hash ] ```Final output ``` | table data.type, seen_ja4_hash, data.user_name
As per detection, JA4 fingerprints are considered to be suspicious due to being associated with abnormal activities, i.e. credential stuffing. To eliminate the list of JA4 fingerprints and focus on only the high-risk fingerprints, we also recommend applying these two additional filters:
- Are these fingerprints present in the public database of fingerprints for common applications?
- When a threat intelligence stream is available, have these JA4 fingerprints been associated with some malicious infrastructure?
Filtering out these additional criteria gives you higher-fidelity results and reduces the number of false positives. Alternatively, the logins_from_suspicious_tls_fingerprints.yml can be tuned for JA3 fingerprints. Even though JA3 suffers from randomization recently introduced into Chrome, it can still be an adequate signal when there’s high assurance in attribution of these JA3 fingerprints to previous malicious activities.
When JA4 fingerprints are considered to give high-fidelity results, meaning that there is little risk of affecting legitimate users, we also suggest blocking these fingerprints with the Auth0 Network/Tenant Access Control List.
Monitoring for authorization request abuse
Authorization request abuse is often overlooked. Before engaging in complex credential stuffing or MFA exploitation, threat actors often begin with simple, high-volume probing to map your attack surface, discover misconfigurations, and test for known vulnerabilities. An instance of such activities is malformed requests or the use of standard URLs. Monitoring these seemingly minor failures and deviations provides early warning of an impending, more focused attack.
Surge in failed authorization requests due to wrong parameters
High volumes of failed authorization requests can be a sign of both probing and denial-of-service attempts. The detection many_failed_authorization_requests.yml looks for a surge in failures where a request for authorization is malformed due to invalid values for standard parameters.
An adversary may be trying to:
- Execute injection attacks: A standard step, threat actors start with basics like SQL injections, remote code execution, or cross-site scripting.
- Harvest authorization codes or tokens: By systematically attempting to find a working combination of OAuth 2.1 parameters, e.g., discovering a Proof of Key Code Exchange (PKCE) downgrade bypass or
redirect_urivalidation vulnerabilities. - Conduct a denial-of-service (DoS) attack: By depleting your tenant’s rate limits with a flood of malformed requests.
The detection specifically monitors for and counts failures related to the following OAuth 2.1 parameters:
- Unregistered or invalid redirect URI
- Unrecognized audience or client
- Unsupported response type or challenge type
The detection alerts when the count for any of these categories exceeds a specified threshold, providing the exact malformed values for investigation.
index=auth0 data.tenant_name="{your-tenant-name}" data.type = f data.description IN ("*is not supported by this server*", "*mismatch*", "*Unknown client*", "*Unsupported response type*", "*Service not found*", "The redirect_uri parameter is not valid*") | fields data.description ```Collect injection strings``` | rex field=data.description "Callback URL mismatch. (?<mismatch_uri>[^ ]+)" | rex field=data.description "Code challenge type (?<malformed_challenge_type>[^ ]+) is not supported by this server." | rex field=data.description "Unknown client: (?<malformed_client>.*)" | rex field=data.description "Unsupported response type: (?<malformed_response_type>.*)" | rex field=data.description "Service not found: (?<malformed_audience>.*)" | rex field=data.description "The redirect_uri parameter is not valid: "(?<malformed_redirect>[^"]*)"" ```Prepare output: list injected strings and calculate volumes for each parameter of the authorization request``` | stats values(mismatch_uri) as mismatch_uri, values(malformed_challenge_type) as malformed_challenge_type, values(malformed_client) as malformed_client, values(malformed_response_type) as malformed_response_type, values(malformed_audience) as malformed_audience, values(malformed_redirect) as malformed_redirect | transpose | rename "column" as field, "row 1" as value | eval count=mvcount(value) | table field, value, count | where count > {threshold}
Use of the canonical Auth0 tenant URL
The majority of Auth0 customers register a custom domain (e.g., login.yourcompany.com) to provide a seamless and trusted user experience. Once a custom domain is onboarded and active, there’s very little legitimate reason for requests to target the original Auth0 tenant name URL (e.g., yourtenant.auth0.com).
This indicator is often overlooked, but it can be a red flag. Attackers may default to using the canonical Auth0 URL in their scripts, or they may use it to bypass security controls that only monitor the custom domain, e.g. web application firewall (WAF).
The detection use_of_auth0_tenant_name_url.yml helps bring visibility to this activity:
index=auth0 data.tenant_name="{your-tenant-name}" data.type IN (s, f, fs, ss, w, fp, scoa, fcoa, sepft, fepft) data.hostname="{your-tenant-url}" | fields data.client_id, data.client_name, data.log_id | table _time, data.client_id, data.client_name, data.log_id
By filtering for successful and failed authentication events that specifically use your tenant's canonical URL, you gain critical insight into potential attempts to bypass your established user flow.
Thanks to Vikas Jayaram for suggesting to include this indicator in the catalog.
Spotting MFA exploits: Misuse and volume-based attacks
MFA is critical, but attackers constantly find ways to execute MFA exploits. The following four detections monitor abnormal bursts of MFA challenge requests and suspicious enrollment patterns.
Misuse of MFA factors (phone numbers and Auth0 Guardian)
Threat actors reuse the same phone number or a single Auth0 Guardian app installation across multiple compromised accounts.
- multiple_phone_numbers_are_registered_as_mfa.yml
- same_guardian_app_is_registered_for_mfa_in_multiple_users.yml
While there are legitimate scenarios for multiple users sharing a phone number (like family members or one person registering two users with work and personal emails), an excessive number of users attached to the same device or phone number can be an indication of accounts being compromised by a single actor; or a precursor to volume-based attacks like a toll fraud.
Both detections provide the count of user enrollments by a respective factor, i.e. phone number and Auth0 Guardian app identifier, for example:
index=auth0 data.tenant_name="{your-tenant-name}" data.description="Guardian - Enrollment complete (sms)" data.type=gd_enrollment_complete | fields data.details.authenticator.phone_number, data.user_id, data.ip | stats dc(data.user_id) as users_count values(data.ip) by data.details.authenticator.phone_number | where users_count > 1
Volume-based MFA exploits (push fatigue and SMS pumping)
MFA push fatigue is an opportunistic attack that targets the user with a flood of MFA requests, hoping the user will accept a push to release the irritation on the user device caused by a constant prompt to approve the push notification.
The detection risk_for_mfa_push_fatigue.yml looks at two aspects of potential threat behavior. In particular, it calculates the number of push notifications sent to users and how quick the push notifications are dispatched:
index=auth0 data.tenant_name="{your-tenant-name}" data.type=gd_send_pn | fields data.user_id, data.ip | stats min(_time) as window_start_time, max(_time) as window_end_time, count as count_pushes_per_user by data.user_id ```| stats max(count_pushes_per_user) as max_count``` | eval window_duration_seconds = window_end_time - window_start_time | eval window_duration_mins = window_duration_seconds/60 | where window_duration_mins<={time_window_threshold} AND count_pushes_per_user > {count_pushes_per_user_threshold} ```Display the information in a table``` | table count_pushes_per_user, window_duration_mins, data.user_id, data.ip
This SMS pumping is usually associated with monetary goals, i.e., toll fraud. This kind of attack often is adjacent to signup fraud involving disposable domains. The detection sms_bombarding.yml looks into a volume of sms sent per user. A possible extension of this detection can be monitoring the total count of sent messages or also accounting for the failure rate.
index=auth0 data.tenant_name="{your-tenant-name}" data.type = gd_send_sms data.description="Guardian - Second factor sms sent" | fields data.user_id ```The period of observations - adjust``` | bin _time span=1h | stats count as sms_per_user by data.user_id | where sms_per_user > {threshold_for_max_sms}
These detections capture the signals of a high-volume attack, allowing you to stop the activity before it succeeds or causes significant toll costs. Monitoring the volume of MFA requests per user, per IP, or across the tenant is key to identifying these abuses.
Next steps
Security is a shared responsibility, and the flexibility of the Auth0 platform gives you the tools to elevate your defense. These eight detections from the Auth0 Detection Catalog represent best practices gleaned from actual threat intelligence and real-world incidents.
We encourage you to:
- Adopt and deploy: Take these detections and implement them in your SIEM using your Auth0 Log Streaming.
- Contribute: The catalog is an open-source community effort. If you’ve developed a valuable detection, we invite you to contribute it back to the Auth0 Detection Catalog on GitHub to help the entire community stay secure.
Start streaming your logs today and take control of your threat detection posture!
About the author

Maria Vasilevskaya
Principal Security Engineer
