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.
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?
| Misconfiguration | What the attacker does | Telemetry / audit fingerprint | Defense |
|---|---|---|---|
Loose redirect_uri validation | Redirects code/token to their server | redirect_uri not exact-matching the allowlist | Exact-match allowlist |
| No PKCE | Intercepts the authorization code | Public client exchanging codes without PKCE | PKCE for all clients |
Missing/!validated state | CSRF / login-fixation | Authorize flow with no/!checked state | Validate state |
| Implicit flow | Reads token from the URL fragment | response_type=token in authorize requests | Use code flow + PKCE |
| Over-broad scopes / pre-consent | Uses a token beyond its need | Token scope exceeding baseline | Least-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.
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.
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 The consent and token-abuse detections in SPL and KQL
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.
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 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.
What is an illicit consent grant, and why does it beat MFA?
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:
| Control | Stops illicit consent? | Why |
|---|---|---|
| Phishing-resistant MFA | No | The user legitimately authenticated; MFA succeeded |
| Password reset | No | The refresh token is independent of the password |
| AiTM detection | No | There is no proxy; the domain is genuine |
| Conditional Access | Partially | Can restrict, but the sign-in is legitimate |
| Admin consent workflow | Yes | Removes the user’s ability to grant it at all |
| App-consent policy restrictions | Yes | Limits 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 positive | Which rule it hits | Why it happens | Resolution |
|---|---|---|---|
| Legitimate new SaaS rollout | New-app consent | The business genuinely adopted a tool | Correlate with procurement or change records |
| Corporate VPN egress | New-origin token use | Egress ASN differs from the user’s baseline | Allowlist corporate ASNs |
| Mobile carrier NAT | New-origin token use | Carrier pools span regions | Exclude known mobile ASNs |
| Developer testing | Off-allowlist redirect | Localhost and staging redirects during development | Separate non-production tenant |
| Vendor integrations | High-scope consent | Genuine integrations need broad scopes | Admin-consent workflow with a register |
| Browser extensions | New-app consent | Some request mail or drive scopes | Treat as software approval, not a security exception |
| Token refresh after travel | New-origin token use | The user genuinely moved | Correlate 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.
How do you triage an OAuth consent or token alert?
- 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.
- 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. - Revoke the grant and delete the service principal. This — not a password reset — is the containment action. Confirm the refresh tokens are invalidated.
- Enumerate every user who consented to the same application. Illicit consent campaigns are broad; one alert usually means several victims.
- Pull the application’s activity. Mail read, file download, and message-send actions performed under the grant tell you the actual exposure.
- Check for follow-on persistence — mail-forwarding rules, inbox rules, new device registrations, and additional grants created under the first one.
- Block the application tenant-wide, so re-consent is not possible while you investigate.
- 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:
- 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,/../). - PKCE: confirm it is required for all clients, not just public ones.
- state: confirm it is generated, bound to the session, and validated on return.
- Flow: confirm the implicit flow is disabled; use authorization code + PKCE.
- Token exchange: confirm
redirect_uriis re-validated at the code-exchange step. - 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
- Enforce exact-match
redirect_urivalidation (scheme, host, port, path) per client. - Ban wildcard and regex redirect matching; require HTTPS.
- Require PKCE for all clients; validate
state; disable the implicit flow. - Re-validate
redirect_uriat the token-exchange step. - Apply least-privilege scopes; remove unnecessary pre-consent grants.
- Log authorize requests; alert on off-allowlist or open-redirect
redirect_uri. - Baseline token use (network/geo/scope) and alert on deviation after issuance.
- 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 TrainingStart 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.