OAuth 2.0 Security

Authorization framework, not an authentication protocol, but bolted onto login everywhere anyway. That mismatch is where most bugs live. Spec is deliberately loose, almost everything security-relevant is optional, so real-world implementations leak. Sensitive material (codes, tokens) travels through the browser in most flows, giving you an interception surface a normal password login doesn’t have.

Three parties: client app (wants the data), resource owner (the user), OAuth service (auth server + resource server). When it’s used for login, client swaps received user data in place of a password. Server has nothing to compare against, so it implicitly trusts whatever comes back. That implicit trust is the recurring theme.

Recon first

Flow cheat (what’s browser-visible vs back-channel)

redirect_uri validation (the big one)

Server decides where to send the code/token based on redirect_uri. Weak validation here means you redirect the victim’s code/token to a host you control. Highest-impact and most common OAuth bug class. Even without knowing client_secret, in the auth code flow you can often bounce a stolen code through the legit /callback and let the app do the exchange for you, logging you in as the victim.

Things to fuzz on redirect_uri:

https://default-host.com
&@foo.evil.net#@bar.evil.net/
https://[email protected]/
https://evil.net\@legit.com/
...&redirect_uri=https://legit.com/callback&redirect_uri=https://evil.net

When external hosts are properly blocked, pivot inside the whitelisted domain

Whitelist holds, so stop trying to escape it and abuse it instead. Point redirect_uri at another page on the allowed domain that leaks the code/token off-site.

https://client.com/oauth/callback/../../some/other/path

resolves server-side to https://client.com/some/other/path.

For implicit, remember stealing the token isn’t just account login on the client. Token is valid against the resource server directly, so you can call /userinfo and pull data the client UI never exposes.

Missing/weak state (CSRF)

state is the CSRF token for the OAuth flow. Unguessable, session-bound value echoed back with the code. No state, or predictable/static state ("state", sequential ints), means you can initiate a flow yourself and trick the victim’s browser into completing it.

Scope upgrade

Token issued for a narrow scope, you widen it past what the user consented to.

POST /token...grant_type=authorization_code&code=...&scope=openid%20email%20profile

Defence-side check for reporting: resource server must verify the token was issued to the same client_id making the request, and that requested scope matches granted scope.

Unverified user registration (provider trust)

Client blindly trusts provider-supplied identity data. If the provider lets you register an account with an unverified email, register one matching the victim’s known email at the provider, then log into the client “as” them. Pure provider-side data-integrity failure, client can’t tell.

Token hygiene issues (report even if not directly exploitable in the box)

OpenID Connect specifics

Identity layer on top of OAuth. Stricter spec so fewer glaring bugs, but it’s still OAuth underneath so all the above still applies. Adds standardized scopes (openid mandatory, then profile/email/address/phone) and the id_token response type (a JWS-signed JWT carrying claims + auth context).

Pentest checklist

Defence quick reference

Sources / see more