APIs & Auth

OAuth Hacking: The Complete Guide to OAuth and OIDC Attacks

How OAuth breaks: redirect_uri manipulation, missing state CSRF, implicit token leaks, account-linking takeover, and how to prevent it. With free practice.

Published

OAuth Hacking: The Complete Guide to OAuth and OIDC Attacks

OAuth 2.0 is a delegation protocol: it hands one app limited access to your data on another without sharing your password, and OpenID Connect is the identity layer bolted on top for logging in with Google or GitHub. OAuth is rarely broken by cryptography. It is broken by validation gaps the integrating app leaves open: a redirect_uri that is not matched exactly, a missing state, a token that leaks through the browser, an account-linking flow that trusts an unverified email. These bugs pay out because they chain straight to account takeover, and the mistakes are predictable and testable by hand. Want to feel it rather than read about it? Sign up free and try it on a live target.

What is OAuth (and OIDC)?

The mental model in one sentence: OAuth delegates access, OIDC proves identity. OAuth lets a client obtain a token to act on your behalf against a resource server. OIDC adds a signed ID token that says who you are, which is what powers social login. Four roles appear in every flow:

  • Resource owner: you, the user.
  • Client: the app requesting access.
  • Authorization server (AS): the provider that authenticates you and issues codes and tokens (Google, Okta).
  • Resource server: the API that accepts the access token.

The attack surface is almost never the AS's crypto. It is how the client and AS validate the parameters moving between them.

The grant types

OAuth has several flows, and each has its own attack surface. Authorization code (with PKCE) is the modern default, implicit is legacy and leaks tokens in the URL, client credentials is machine-to-machine, and device code is for input-constrained devices. See OAuth grant types.

OpenID Connect

OIDC returns an ID token, a signed JWT carrying sub, iss, aud, exp, and, when the client requests it, nonce. It is discovered at /.well-known/openid-configuration and verified against a JWKS. Where those trust assumptions break (issuer confusion, JWKS spoofing, alg tricks), identity itself is forgeable. See OpenID Connect.

redirect_uri manipulation

The single most common OAuth flaw. The AS must match redirect_uri against the registered value exactly. When it does a prefix or substring check instead, you bend the callback to your own endpoint and capture the code. See redirect_uri manipulation.

The state parameter and CSRF

state binds the callback to the user's session. Drop or ignore it and you get login CSRF: the attacker forces their own account onto the victim's session. See the state parameter and CSRF.

Implicit flow token leakage

The legacy implicit flow returns the access token in the URL fragment, where it leaks via browser history, client-side scripts that read location.hash, and logs, and can be exfiltrated by chaining an open redirect. See implicit flow token leakage.

Account linking abuse

"Link your Google account" flows that trust an unverified email claim enable pre-account-takeover: the attacker's identity and the victim's end up merged. See account linking abuse.

Device flow abuse

The device grant has the user approve a short user_code, while the device polls with its device_code for a token. An attacker can phish the victim into approving the attacker's user_code, then poll with the attacker's device_code for the victim's token. See device flow abuse.

A worked example

Say an app offers "Log in with ExampleID." You map the flow by watching the authorization request the client sends:

GET /authorize?response_type=code&client_id=app123&redirect_uri=https://app.tld/callback&scope=openid%20email&state=xyz HTTP/1.1
Host: exampleid.com

You first test how the server matches redirect_uri. A strict server requires it to equal the registered https://app.tld/callback exactly. A common weak check only confirms the value starts with https://app.tld, missing the path boundary, so a lookalike subdomain slips through because app.tld.attacker.tld still starts with app.tld:

GET /authorize?response_type=code&client_id=app123&redirect_uri=https://app.tld.attacker.tld/callback&scope=openid%20email&state=xyz HTTP/1.1
Host: exampleid.com
HTTP/1.1 302 Found
Location: https://app.tld.attacker.tld/callback?code=SplxlOBeZQ&state=xyz

The authorization server accepted a host that merely starts with app.tld, so the code landed on attacker.tld. Now redeem it at the token endpoint before it expires:

POST /token HTTP/1.1
Host: exampleid.com
Content-Type: application/x-www-form-urlencoded
 
grant_type=authorization_code&code=SplxlOBeZQ&redirect_uri=https://app.tld.attacker.tld/callback&client_id=app123
HTTP/1.1 200 OK
Content-Type: application/json
 
{"access_token":"2YotnFZF...","token_type":"Bearer","id_token":"eyJhbGci..."}

A real token endpoint also requires client authentication here: a confidential client sends its client_secret, and a public client sends the PKCE code_verifier. This example omits them to isolate the redirect_uri bypass. With the right auth (or a client that lacks PKCE), you now hold the victim's tokens. That is the arc: map the flow, test exact redirect_uri matching, bypass a weak validator, capture the code, and redeem it for a session.

Prevention

  • Exact-match redirect_uri allow-lists. No wildcards, no prefix or substring checks, no open redirects on allowed hosts.
  • Mandatory PKCE on every public client, binding the code to a per-flow verifier.
  • Enforce and verify state (CSRF) and nonce (OIDC replay) on every flow.
  • Drop implicit and hybrid. Use authorization code only and keep tokens out of the URL.
  • Verify email ownership before account linking.
  • Validate ID tokens: signature, iss, aud, exp, and nonce, and pin the JWKS.

The full detail is in preventing OAuth attacks.

Where it connects

OAuth sits at the center of the identity attack surface. Its ID token is a JWT, so the alg:none and jku tricks there apply directly to OIDC. Its state flaw is a CSRF, its scope abuse is broken access control, and its OIDC request_uri can drive SSRF. And it is the mechanism behind social login, which sits alongside the password flows in authentication.

Where to go next

Work through the cluster in order, or jump to what you need. Start with OAuth grant types, then OpenID Connect and redirect_uri manipulation. When you want every payload in one place, the OAuth cheat sheet has them. And when you are ready to break something real, sign up free and start hacking.