Detection Engineering

OAuth Misconfiguration: Defensive Review

An OAuth misconfiguration defensive review — redirect_uri validation, PKCE and state, detecting off-allowlist redirects and token anomalies, aligned to RFC 9700.

Two dark server nodes joined by a glowing cyan link with one red broken segment representing a redirect hijack

OAuth misconfiguration almost always concentrates in one parameter: redirect_uri. If the authorization server fails to validate it with an exact match against a per-client allowlist, an attacker can route a victim’s authorization code or token to a server they control and take over the account — often without ever knowing the client secret. A defensive OAuth review is mostly a hunt for loose redirect_uri validation, missing PKCE, and unvalidated state. This guide ships the review checklist plus detection for off-allowlist redirects and token anomalies.

Loose redirect handling underlies real breaches — Salt Labs’ Booking.com/Kayak findings and the 2025 Salesloft–Drift token abuse both trace to OAuth implementation gaps. The standard to align to is RFC 9700, the January 2025 OAuth 2.0 Security Best Current Practice. The flow maps to MITRE ATT&CK T1528 — Steal Application Access Token, T1550.001, and T1078.

What is an OAuth misconfiguration?

An OAuth misconfiguration is a gap in how an authorization server or client implements the OAuth flow that lets an attacker obtain a code, token, or account access they should not. The flow itself is sound; the failures are in validation — which redirect_uri values are accepted, whether state and PKCE are enforced, and how tokens are scoped and stored. Because OAuth tokens are identity, a misconfiguration here is an account takeover, much like a JWT misconfiguration.

The redirect_uri is the crown jewel: it is where the server sends the code or token after login. Validate it loosely and you hand the attacker a way to redirect that secret to themselves.

What are the common OAuth misconfigurations?

MisconfigurationWhat the attacker doesTelemetry / audit fingerprintDefense
Loose redirect_uri validationRedirects code/token to their serverredirect_uri not exact-matching the allowlistExact-match allowlist
No PKCEIntercepts the authorization codePublic client exchanging codes without PKCEPKCE for all clients
Missing/!validated stateCSRF / login-fixationAuthorize flow with no/!checked stateValidate state
Implicit flowReads token from the URL fragmentresponse_type=token in authorize requestsUse code flow + PKCE
Over-broad scopes / pre-consentUses a token beyond its needToken scope exceeding baselineLeast-privilege scopes

The unifying lesson: OAuth security is strict validation plus least privilege, and the detectable signals are off-allowlist redirects at request time and anomalous token use after issuance.

How to detect OAuth misconfiguration abuse

Two signals matter: an authorize request that names a redirect_uri outside the client’s allowlist, and a token used from somewhere it shouldn’t be.

Off-allowlist redirect_uri at the authorize endpoint

If you log authorize requests with the requested redirect_uri and the client’s registered allowlist, any mismatch is an attack or a broken integration.

Sigma OAuth Authorize Request With an Off-Allowlist redirect_uri
title: OAuth Authorize Request With an Off-Allowlist redirect_uri
id: 8a1f4c92-darkpwn-illustrative
status: experimental
logsource:
  product: application
  service: oauth
detection:
  selection:
    endpoint|contains: '/authorize'
  suspect:
    redirect_uri|contains: ['@', '%2f', '%2F', '/../', 'localhost', '..']
  mismatch:
    redirect_uri_allowlisted: 'false'
  condition: selection and (suspect or mismatch)
falsepositives:
  - Newly registered legitimate callback URLs (update the allowlist, then this clears)
level: high

Token used from a new network shortly after issuance

Stolen tokens generate the same API traffic as legitimate ones, so the signal is behavioral — a token issued to one network appearing on another minutes later.

SPL OAuth Token Used From a New Network Shortly After Issuance
index=oauth (event=token_issued OR event=token_used)
| transaction token_id maxspan=15m
| eval issue_net=mvindex(src_subnet,0), use_net=mvindex(src_subnet,-1)
| where issue_net!=use_net
| table _time, client_id, token_id, issue_net, use_net, scope

Redirect-URI abuse is the classic flaw; illicit consent grants are the variant that dominates real incidents, because they need no vulnerability at all — just a user clicking Allow.

SPL Illicit Consent Grant — New App With High-Privilege Scopes
index=identity sourcetype=oauth:consent
| eval risky = if(match(scopes, "(?i)(mail\.read|mail\.send|files\.readwrite|offline_access|directory\.readwrite|user\.read\.all)"), 1, 0)
| stats min(_time) AS first_grant, dc(user) AS users, values(scopes) AS granted,
        values(publisher) AS publisher by client_id, app_name
| eventstats min(first_grant) AS app_first_ever by client_id
| eval is_new_app = if(app_first_ever >= relative_time(now(), "-7d"), 1, 0)
| where is_new_app=1 OR users >= 5
| eval severity = case(
    users >= 20, "critical",
    match(granted, "(?i)offline_access") AND users >= 5, "high",
    true(), "medium")
| table first_grant, app_name, client_id, publisher, users, granted, severity
KQL Refresh Token Used From an Origin the User Has Never Used
let baseline = 30d;
let known =
    SigninLogs
    | where TimeGenerated between (ago(baseline) .. ago(1h))
    | summarize by UserPrincipalName, AutonomousSystemNumber;
SigninLogs
| where TimeGenerated > ago(1h)
| where AuthenticationProtocol == "oAuth2" or ResourceDisplayName != ""
| join kind=leftanti known on UserPrincipalName, AutonomousSystemNumber
| summarize Apps = make_set(AppDisplayName), Ips = make_set(IPAddress),
            Count = count() by UserPrincipalName, AutonomousSystemNumber
| where Count >= 2
| order by Count desc

The consent query encodes the two properties that make an illicit grant campaign distinctive: the application is new to your tenant, and it requests offline_access — because the attacker wants a refresh token that survives password resets and outlives the session. Neither property is suspicious alone; together they are the signature.

An illicit consent grant is an attack in which the user is shown a genuine OAuth consent screen from your real identity provider, for an application the attacker controls. There is no phishing proxy and no credential theft. The user authenticates normally — with MFA, on the legitimate domain — and simply approves an application.

That is why it defeats controls built for credential phishing:

ControlStops illicit consent?Why
Phishing-resistant MFANoThe user legitimately authenticated; MFA succeeded
Password resetNoThe refresh token is independent of the password
AiTM detectionNoThere is no proxy; the domain is genuine
Conditional AccessPartiallyCan restrict, but the sign-in is legitimate
Admin consent workflowYesRemoves the user’s ability to grant it at all
App-consent policy restrictionsYesLimits which scopes users may approve

Restricting user consent is the control, and it is a tenant setting rather than an engineering project. Allow users to consent only to verified publishers and low-impact scopes, and route everything else through an admin approval workflow. The workflow is the point — a blanket block with no approval path gets disabled the first time it obstructs real work.

Which false positives will you actually see?

False positiveWhich rule it hitsWhy it happensResolution
Legitimate new SaaS rolloutNew-app consentThe business genuinely adopted a toolCorrelate with procurement or change records
Corporate VPN egressNew-origin token useEgress ASN differs from the user’s baselineAllowlist corporate ASNs
Mobile carrier NATNew-origin token useCarrier pools span regionsExclude known mobile ASNs
Developer testingOff-allowlist redirectLocalhost and staging redirects during developmentSeparate non-production tenant
Vendor integrationsHigh-scope consentGenuine integrations need broad scopesAdmin-consent workflow with a register
Browser extensionsNew-app consentSome request mail or drive scopesTreat as software approval, not a security exception
Token refresh after travelNew-origin token useThe user genuinely movedCorrelate with a prior sign-in from the same region

The developer row is worth solving structurally. Wildcard and localhost redirect URIs registered “temporarily” in production tenants are a recurring root cause, and the fix is a separate non-production tenant rather than a permanent exception in the live one.

  1. Determine whether a grant was actually created. A consent prompt shown is not a consent granted; check for the service principal and its permission grant.
  2. Read the scopes. offline_access, mail read/send, and files read-write are the ones that matter — they define exactly what the attacker can do, with no guesswork required.
  3. Revoke the grant and delete the service principal. This — not a password reset — is the containment action. Confirm the refresh tokens are invalidated.
  4. Enumerate every user who consented to the same application. Illicit consent campaigns are broad; one alert usually means several victims.
  5. Pull the application’s activity. Mail read, file download, and message-send actions performed under the grant tell you the actual exposure.
  6. Check for follow-on persistence — mail-forwarding rules, inbox rules, new device registrations, and additional grants created under the first one.
  7. Block the application tenant-wide, so re-consent is not possible while you investigate.
  8. Fix the consent policy. The alert has told you users can grant high-impact scopes, which is the actual defect.

Step 3 is the one that decides whether the incident ends. Password resets and MFA re-enrollment do nothing here; the grant is a separate credential and it persists until you delete it.

How to run an OAuth defensive review

Audit each client and the authorization server:

  1. redirect_uri: confirm exact-match validation against a per-client allowlist — no wildcards, no regex, no path-prefix matching. Test bypass payloads (@evil.com, app.com.evil.com, %2f, /../).
  2. PKCE: confirm it is required for all clients, not just public ones.
  3. state: confirm it is generated, bound to the session, and validated on return.
  4. Flow: confirm the implicit flow is disabled; use authorization code + PKCE.
  5. Token exchange: confirm redirect_uri is re-validated at the code-exchange step.
  6. Config hygiene: flag wildcard/localhost redirects, unnecessary pre-consent, and over-broad default scopes.

How to prevent OAuth misconfiguration

  • Scope least privilege — request and grant only the scopes the client needs.
  • Store tokens safely — prefer HttpOnly cookies over local storage; implement revocation.
  • For mobile, use platform link verification (Android App Links, iOS Associated Domains) instead of bare custom schemes.

Common OAuth detection mistakes

  • Not logging authorize requests. Off-allowlist redirects go unseen.
  • Content-based token detection. Stolen and legitimate tokens look identical — you need behavioral baselines.
  • Treating a password reset as containment. An OAuth grant is a separate credential and survives it; you must delete the service principal and revoke the grant.
  • Ignoring illicit consent grants. They need no vulnerability, defeat phishing-resistant MFA, and are the variant that dominates real incidents.
  • Letting users consent to any scope. The defect is the consent policy, not the user.
  • Wildcard or localhost redirect URIs in production tenants. Registered “temporarily” during development and never removed.
  • Regex/wildcard redirect validation. The most common bypass source; use exact match.
  • Treating it as a one-time review. Misconfigurations sit for months; review configs continuously.

OAuth misconfiguration checklist

  1. Enforce exact-match redirect_uri validation (scheme, host, port, path) per client.
  2. Ban wildcard and regex redirect matching; require HTTPS.
  3. Require PKCE for all clients; validate state; disable the implicit flow.
  4. Re-validate redirect_uri at the token-exchange step.
  5. Apply least-privilege scopes; remove unnecessary pre-consent grants.
  6. Log authorize requests; alert on off-allowlist or open-redirect redirect_uri.
  7. Baseline token use (network/geo/scope) and alert on deviation after issuance.
  8. Store tokens in HttpOnly cookies; support revocation; align to RFC 9700.

Why is this the attack that survives your MFA investment?

OAuth consent abuse deserves particular attention because it defeats the control most organisations consider their strongest, and it does so without breaking it.

The mechanism is that the user authenticates correctly — real credentials, real second factor, real success — and then grants an application a scope. The attacker never possesses the password, never intercepts the token exchange, and never triggers an impossible-travel or unfamiliar-device signal, because from the identity provider’s perspective nothing anomalous happened. A legitimate user made a legitimate authorization decision.

What the attacker holds afterwards is a refresh token, and its properties are what make this so durable:

  • It survives a password reset. The reflex response to compromise does nothing here.
  • It survives an MFA re-enrolment, for the same reason.
  • It is long-lived by design, so the access persists for as long as the grant does.
  • It is used from the application’s infrastructure, so it does not look like the user logging in from somewhere strange.

The only reliable remediation is revoking the grant, and that is a different action from everything in the standard compromise runbook. Teams that respond to this incident with a password reset and an MFA reset conclude they have contained it while the attacker’s access is entirely unaffected — which is the failure mode worth rehearsing before it happens.

What should you restrict before you try to detect?

Detection here is genuinely difficult, and a large share of the risk is removable by configuration first — which is a much better ratio of effort to protection.

Turn off unrestricted user consent. This is the single highest-leverage setting. When any user can grant any application access to their data, the attack requires nothing but a convincing prompt. Requiring administrative approval for anything beyond trivial scopes converts a phishing success into a support ticket.

Allow consent only for verified publishers, and only for low-risk scopes. That preserves the convenience case — the well-known productivity integration — while removing the anonymous application registered yesterday.

Review existing grants, not just future ones. Whatever policy you set applies going forward; the grants already issued are unaffected, and in most tenants that backlog contains applications nobody recognises. This is a one-time audit with a recurring review, and it is usually where the surprises are.

Cap token lifetimes where the platform permits it, so that a grant that escapes notice has a bounded rather than indefinite life.

The framing worth carrying: this is a permissions problem wearing an authentication costume. Investment in stronger authentication does not touch it. Investment in who may grant what, to whom, does.

What does a healthy application inventory look like?

Most tenants cannot answer “which applications hold access to our data, granted by whom, with what scopes” — and that question is the whole review. Building the inventory once and keeping it current is what turns OAuth from an opaque risk into a managed one.

Four attributes per registered or consented application:

  • Publisher and verification status. An unverified publisher is not automatically malicious and it is the strongest single filter for triage.
  • Scopes granted, and whether they are read or write. Mail-read is an exfiltration risk; mail-send is an internal-phishing platform that inherits your domain’s reputation.
  • Who consented, and how many users. One consent from one user is a different situation from tenant-wide admin consent.
  • Last used. Applications nobody has touched in months are the easiest wins to revoke, and dormant grants are exactly what a patient attacker relies on.

The review cadence that works is quarterly for the full inventory and immediate for any newly-consented application with write scopes. That second trigger is the one worth automating, because the window that matters is measured in hours.

One organisational point: this inventory belongs to whoever owns identity, not to the security team alone. Revoking a grant breaks a workflow for whoever was using it, so a review that cannot make that call quickly turns into a list nobody acts on.

One caution on revocation: revoking a grant does not always invalidate tokens already issued under it, depending on platform and token type. Confirm the behaviour for your identity provider before treating revocation as containment, and where access tokens outlive the grant, plan for that residual window in the incident timeline rather than discovering it afterwards.

Finally, treat the consent prompt itself as a user-facing security control worth teaching. Staff are asked to make an authorization decision in a dialog that looks routine, with no context about what the scopes mean. Naming that moment in awareness training — an app is asking for permission to read your mail; who is asking, and why now — is one of the few interventions that addresses the attack at the point it succeeds, and it costs nothing.

A final note on scope creep in the other direction: not every consented application is a risk, and treating the inventory as a list of suspects wastes the review. Most entries are tools someone genuinely needed. The ones worth a hard look are narrow and identifiable — unverified publisher, write scopes, broad tenant consent, or no recorded use since the day it was granted.

Triage on those attributes rather than on volume, and the inventory stays a working tool instead of becoming a backlog nobody opens.

The takeaway

An OAuth misconfiguration review is mostly a redirect_uri review: exact-match validation, PKCE everywhere, validated state, and re-validation at token exchange, all aligned to RFC 9700. Detect off-allowlist redirects at request time and anomalous token use after issuance. Pair this with JWT misconfiguration detection and GraphQL authorization for full API-security coverage, harden the human side with YubiKey deployment and phishing detection beyond DMARC, catch a grant or token that has already been stolen with session hijacking detection, and keep client secrets out of your repositories with secrets detection. Or browse the Detection Engineering pillar.

Training & tools referenced

Disclosure: Some links below are affiliate links. If you buy through them, darkpwn may earn a commission at no extra cost to you. We only recommend training and tools we actually use in our own lab, and affiliate links never influence editorial coverage.

  • TryHackMeAuthorized labs to practice OAuth and authentication security reviewSecurity Training
    Start training

Frequently asked questions

What is the most dangerous OAuth misconfiguration?

Loose redirect_uri validation. If the authorization server does not enforce exact-match validation against a per-client allowlist, an attacker can route the authorization code or token to a server they control and take over the account. Validate the full redirect_uri — scheme, host, port, and path — with no wildcards or regex.

How do you detect OAuth redirect_uri abuse?

Log OAuth authorize requests and alert on any redirect_uri that is not an exact match to the client's registered allowlist, on open-redirect patterns (@, %2f, extra subdomains), and on tokens used from a new network or geo shortly after issuance. Audit client configs for wildcard or localhost redirects and unnecessary pre-consent.

Does PKCE prevent authorization code interception?

Yes — PKCE binds the authorization code to a code_verifier the attacker does not have, so an intercepted code cannot be exchanged for a token. RFC 9700 (2025) recommends PKCE for all clients, public and confidential.

What is RFC 9700?

RFC 9700 (January 2025) is the updated IETF OAuth 2.0 Security Best Current Practice. It codifies lessons from real breaches — exact redirect_uri matching, PKCE for all clients, avoiding the implicit flow, and validating the state parameter.