Internet Protocols · Identity Protocols

Delegated Authority: What OAuth's Four Roles Actually Separate

Why OAuth replaces password sharing with a scoped, revocable token, and how the split between authorization server and resource server makes that possible.

Before OAuth, a client that wanted to act on a user’s behalf on another service asked for the user’s password. The client stored it, replayed it, and in doing so acquired every capability the user had, for as long as the password remained unchanged. There was no way to grant partial access, no way to revoke one client without breaking all of them, and no way for the resource service to distinguish the user from the client. OAuth 2.0 is an answer to that specific arrangement, and almost every rule in the specification follows from trying to avoid reintroducing it.

Three properties a password cannot provide

The problem with handing over a password is not secrecy in transit, which TLS addresses. It is that a password is an unbounded, long-lived, all-or-nothing credential. Three separate failures follow.

The first is scope. A password authenticates a person; it does not describe what the client intends to do. A photo-printing service that receives a mail provider’s password can read mail, send mail, and change the account recovery address, because the credential carries the whole authority of the account.

The second is revocation granularity. Changing the password invalidates every client at once, including the ones the user still wants. Leaving the password unchanged means the compromised client keeps working indefinitely.

The third is attribution. When a request arrives bearing the user’s password, the resource service cannot tell whether the user made it or a client did on the user’s behalf. Audit records cannot distinguish the two, and the service has no basis for applying different policy to each.

A delegated token fixes all three by being a separate credential with its own lifetime, its own set of permissions, and its own identity in the logs. The resource owner’s actual password never leaves the authorization server.

Four roles, and the one boundary that matters

RFC 6749 names four roles. The resource owner is the entity granting access, normally the person. The client is the application requesting it. The authorization server issues tokens after authenticating the resource owner and obtaining consent. The resource server accepts those tokens and serves the protected resource.

The split that does the work is between the authorization server and the resource server. They may be the same process on the same host, and in small deployments often are, but the specification requires that they be logically distinct. The reason is that token validation must not require access to the user credential store. A resource server that had to check a password on every request would be back where it started: it would need the password, and it could not accept a third-party client without accepting the password too.

Because validation is independent, the resource server can be replicated, moved to a different trust domain, or operated by a different organisation. The only thing it shares with the authorization server is the ability to recognise a token, either by asking the authorization server about it or by verifying a signature over claims the authorization server made.

The client is likewise not a trusted party in the general case. OAuth distinguishes confidential clients, which can keep a secret because they run on a server the user does not control, from public clients, which cannot: browser-based applications, mobile applications, and anything whose code and storage are reachable by the user. A secret embedded in a distributed binary is not a secret. Much of the later machinery in OAuth exists because public clients are legitimate participants and cannot be authenticated by a shared key.

Two channels and why the code exists

OAuth separates the user-agent channel from the direct back channel. In the front channel, the client redirects the browser to the authorization server, the user interacts with it, and the browser is redirected back to the client with a result in the URL. Everything in that path is visible to the browser and to anything the browser talks to: the address bar, history, referrer headers, and any extension with page access.

In the back channel, the client makes an HTTPS request directly to the authorization server’s token endpoint. There is no user agent in the middle, the client can authenticate itself, and the response body is not exposed to the browser.

The authorization code flow is the consequence. The front channel carries only a short opaque code, and the token is collected on the back channel in exchange for that code. The code is single-use and short-lived; RFC 6749 recommends a maximum lifetime of ten minutes. An attacker who observes the redirect learns a value that is useless without the ability to complete the exchange, which requires either the client’s secret or, for public clients, the proof-of-possession mechanism described in PKCE.

The same reasoning explains why the implicit flow, which returned the access token directly in the redirect fragment, was deprecated for new applications. A token in a fragment is a bearer credential exposed to the front channel, with a lifetime measured in hours rather than minutes.

What an access token is

An access token is an opaque string as far as the client is concerned. The client must not parse it, must not assume it is a JWT, and must not depend on any structure inside it. It is handed to the resource server as-is.

The exchange at the token endpoint is a form-encoded POST:

POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZXpx

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

The response carries access_token, token_type, and usually expires_in and scope. The token_type value Bearer is defined by RFC 6750 and tells the client how to present the token, not what the token contains. A resource request then looks like this:

GET /v1/photos?album=42 HTTP/1.1
Host: api.example.com
Authorization: Bearer mF_9.B5f-4.1JqM

RFC 6750 permits three transport methods: the Authorization header, a form-encoded body parameter, and a URI query parameter. The header is the only one that does not put the token somewhere it will be logged, and the query parameter method is explicitly discouraged for that reason. Proxy access logs, browser history, and Referer headers all capture full URLs.

Possession is the whole authentication

The bearer token model is deliberately minimal: any party holding the token can use it. There is no binding to a TLS session, a client key, or a network address. That simplicity is why the model was adopted everywhere, and it is also its central weakness.

Three consequences follow directly. Transport must be TLS, because a token observed in transit is a token usable by the observer. Tokens must not be stored anywhere the client cannot protect them, which for browser clients means memory rather than localStorage where an XSS payload could read them. And tokens must be short-lived, because the window between theft and detection is the window of exposure.

The error responses defined in RFC 6750 are correspondingly narrow. A request without credentials gets 401 with no error code; a malformed or expired token gets 401 with error="invalid_token"; a valid token lacking the necessary permission gets 403 with error="insufficient_scope". The WWW-Authenticate header carries these values, and a scope parameter may indicate what would have sufficed. This is the only place in the base specification where the resource server communicates a permission shortfall, and clients that ignore it produce the familiar pattern of a request that fails permanently with no actionable message.

Sender-constrained tokens are the escape from pure bearer semantics. Certificate-bound tokens, as defined for mutual TLS client authentication, and proof-of-possession schemes such as DPoP make the token unusable by anyone who merely copies it. These are additions to the base model rather than replacements, and they trade deployment complexity for a smaller blast radius.

What the framework leaves to other layers

OAuth 2.0 authorises; it does not authenticate the user to the client. An access token says that some authority was delegated, not who the resource owner is. A client that treats the presence of a token as proof of a person’s identity is making an inference the specification does not support, and that gap is exactly what OpenID Connect fills with a separate token type carrying identity claims.

Two other things are outside the base framework. Revocation is not pushed to resource servers: RFC 6749 defines a revocation endpoint for clients to discard their own tokens, and introspection lets a resource server ask whether a token is still active, but a self-contained token remains valid until it expires unless the resource server checks. And the framework says almost nothing about how consent is presented, so the scope of a grant is limited in practice by what the authorization server chooses to display rather than by anything the protocol enforces.

The mechanism that carries the grant from the browser to the client, and the rules that keep that handoff from being hijacked, are covered in the authorization code flow.

References

  1. RFC 6749: The OAuth 2.0 Authorization Framework — IETF
  2. RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage — IETF
  3. RFC 9700: Best Current Practice for OAuth 2.0 Security — IETF