- RFC 6749: The OAuth 2.0 Authorization FrameworkHardt, Ed. (IETF, October 2012) - obsoletes RFC 5849; updated by RFC 8252, RFC 8996, RFC 9700
- RFC 9700: Best Current Practice for OAuth 2.0 SecurityLodderstedt et al. (IETF, January 2025) - the corrections the 2012 text needs; read every 6749 section beside it
- OpenID Connect Core 1.0Sakimura et al. (February 2014) - the identity layer that answers what OAuth deliberately does not
- oauth.net/2the official site's per-concept reference, each concept mapped to its RFC
Published October 2012, edited by D. Hardt, RFC 6749 defines OAuth 2.0 and obsoletes OAuth 1.0 (RFC 5849). It is a delegation framework: it lets an application (the client) get limited access to a user's protected resources without ever handling the user's password. It is deliberately not an authentication protocol - an access token proves permission to call an API, nothing about who the user is. That gap is what OpenID Connect (February 2014) fills with the ID token: OAuth moves the permission, OIDC proves the person.
Read it as a vocabulary document. It defines four roles, four grant types, two authorization-server endpoints plus one client endpoint, and the token and error semantics everything else inherits - and it says almost nothing about token formats, sessions, or login. Its own interoperability warning (section 1.8) explains the next decade of documents: the framework is "likely to produce a wide range of non-interoperable implementations", and future work was expected to define "prescriptive profiles". RFC 8252 (native apps), RFC 9700 (security BCP), and OAuth 2.1 are exactly that future work.
| Role | Definition (section 1.1) | Example |
|---|---|---|
| Resource owner | "An entity capable of granting access to a protected resource. When the resource owner is a person, it is referred to as an end-user." | You, approving a login screen |
| Client | "An application making protected resource requests on behalf of the resource owner and with its authorization." | A newsletter dashboard calling the Gmail API |
| Authorization server | "The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization." | accounts.google.com |
| Resource server | "The server hosting the protected resources, capable of accepting and responding to protected resource requests using access tokens." | gmail API endpoints |
One subtlety worth keeping: the client asks the resource owner for an authorization grant, and trades that grant at the authorization server for a token. The grant is the abstract thing - "the user said yes" - and the four grant types in section 1.3 are four shapes it can take. Which one you use is decided by the client's type and form factor, not by taste.
Figure 1 of the RFC underlies nearly every later depiction of the flow. Steps (A) and (B) happen at the resource owner's user-agent - "preferably indirectly via the authorization server as an intermediary" (1.2) - which is the seed of the front-channel/back-channel split every modern flow page draws.
The four grant types are four ways to fill in step (B): an authorization code, an implicit hand-off, the resource owner's password, or the client's own credentials.
Section 2.1 splits clients by one property: whether they can maintain the confidentiality of their credentials. A confidential client is "capable of maintaining the confidentiality of their credentials" (think: a server). A publicclient is "incapable of maintaining the confidentiality of their credentials" - everything running on the user's device: browser apps and native apps, where "it is assumed that any client authentication credentials included in the application can be extracted".
This single distinction decides your grant, your redirect rules, and your refresh-token obligations downstream. Section 2.2 separates identification from authentication: "the client identifier is not a secret; it is exposed to the resource owner and MUST NOT be used alone for client authentication". A client_id identifies; it never authenticates.
Section 3 defines two authorization-server endpoints and one client endpoint.The authorization endpoint (3.1) is where the user-agent is sent - the login-and-consent hop. TLS is a MUST (3.1); GET is a MUST-support and POST a MAY (3.1); the endpoint URI MUST NOT include a fragment (3.1). Error handling here is user-facing: on a missing, invalid, or mismatchingredirect_uri (3.1.2.4), or a missing or invalidclient_id (4.1.2.1), the server "MUST NOT automatically redirect the user-agent to the invalid redirection URI" - a bad redirect is shown to the human, never followed.
The redirection endpoint (3.1.2) belongs to the client: it is where the authorization response lands. Public clients MUST register their redirect URIs (3.1.2.2), and the 2012 matching rule is conditional and often misstated: if the registration included the full URI, "the authorization server MUST compare the two URIs using simple string comparison" (3.1.2.3). Partial registrations allowed component-wise matching - an allowance RFC 9700 later removed for everyone except native-app loopback ports, after pattern-matching bugs were exploited in the wild.
The token endpoint (3.2) is the back channel where grants become tokens - used by every grant except implicit, which skips it entirely. POST is a MUST (3.2), TLS is a MUST (3.2), and confidential clients MUST authenticate (3.2.1). One encoding requirement: with HTTP Basic (2.3.1), the client_id and secret are each form-urlencoded before being used as username and password. An id containing + or % fails mysteriously otherwise.
GET https://accounts.example.com/authorize?
response_type=code
&client_id=<client-id>
&redirect_uri=https://sushi.example.com/callback
&scope=orders:read
&state=<per-request-value>Every grant begins as this front-channel redirect; the framework defines the parameters (4.1.1) and the cardinal rule that a badredirect_uri or client_id is shown to the human, never redirected (3.1.2.4, 4.1.2.1). The deep dives add the per-grant parameters: the code flow's version carries state and a PKCE challenge.
POST /token HTTP/1.1 Host: accounts.example.com Authorization: Basic PGNsaWVudC1pZD46PGNsaWVudC1zZWNyZXQ+ (base64 of the words client-id and client-secret) grant_type=authorization_code code=<authorization-code> redirect_uri=https://sushi.example.com/callback
Whatever the grant, success answers in one shared shape (5.1): anaccess_token, a token_type, anexpires_in, and Cache-Control: no-store as a MUST. Failure answers in the shared error grammar below.
Access tokens (1.4) are "a string representing an authorization issued to the client... usually opaque to the client". The format is deliberately out of scope - "defined by companion specifications such as RFC 6750" - which is why the token ecosystem needed years of follow-up work (bearer usage, JWT profiles, introspection). Section 10.10 sets the floor for anything token-shaped: guessability "MUST be less than or equal to 2^(-128)".
Refresh tokens (1.5, section 6) are credentials for the authorization server only - "never sent to resource servers". Issuing one is optional. When refreshing, the requested scope "MUST NOT include any scope not originally granted" - narrowing is allowed, expansion is not. The server MAY rotate the refresh token on each use, in which case the client MUST discard the old one; section 10.4 explains why rotation exists at all: when attacker and legitimate client both present the same rotated token, "one of them will present an invalidated refresh token, which will inform the authorization server of the breach".
Authorization codes (4.1.2, 10.5) get the tightest rules in the framework: "MUST expire shortly after it is issued", with a 10-minute maximum lifetime RECOMMENDED; single-use with a MUST-deny on reuse, and the server SHOULD revoke tokens already issued from a replayed code.
Section 5.2 defines exactly six token-endpoint error codes - there is no invalid_resource, whatever you may have read. All ride on HTTP 400 unless specified otherwise. The authorization endpoint's error response (4.1.2.1) shares invalid_request, unauthorized_client, and invalid_scope with the token endpoint and adds four of its own, delivered as query parameters on the redirect: access_denied, unsupported_response_type, server_error,temporarily_unavailable - the last two existing precisely because a 302 cannot carry an HTTP 500 or 503 to the client.
| Code (5.2) | Means | Typical cause |
|---|---|---|
invalid_request | Missing parameter, unsupported value, repeated parameter, multiple credentials, or otherwise malformed | Broken client code |
invalid_client | Client authentication failed | Wrong secret, wrong Basic encoding; server MAY use 401, and MUST when the client used the Authorization header |
invalid_grant | The grant (code, refresh token, credentials) is invalid, expired, revoked, redirected mismatch, or issued to another client | Replayed or expired code; PKCE verifier mismatch lands here too |
unauthorized_client | Authenticated client may not use this grant type | Client not allowed device flow, say |
unsupported_grant_type | Grant type unknown to the server | Typo in grant_type; server predates the extension |
invalid_scope | Requested scope invalid, unknown, malformed, or exceeds the granted scope | Asking for more on refresh - which section 6 forbids |
The framework's silences shaped the ecosystem as much as its rules. It does not define: the token format or how a resource server validates one (RFC 6750, RFC 7662 introspection, RFC 9068 JWT profile); how native or browser apps receive redirects (RFC 8252, RFC 10017); the security floor (RFC 9700); identity or login (OpenID Connect); or delegation between services (RFC 8693 token exchange). The modern suite page maps all of them - and where OAuth 2.1 lands when the consolidation is done.
The grant used by virtually everything you log into is the authorization-code flow - the framework's abstract figure made concrete, message by message.