Breaking down OAuth 2.0
Running through OAuth 2.0 concepts, grant types, and more
“The OAuth protocol was originally created by a small community of web developers from a variety of websites and other Internet services who wanted to solve the common problem of enabling delegated access to protected resources.” RFC 5849
Auth is an ambiguous shorthand which can refer to either authentication (AuthN) or authorization (AuthZ).
Today, we explore OAuth 2.0. But first, one cannot begin to understand OAuth 2.0 without understanding the context of why AuthN and AuthZ came to be decoupled.
Fundamentals: AuthN and AuthZ
Authentication is concerned with verifying that an entity is who they claim to be; authorization takes that authentication and then identifies what that agent should be entitled to access.
Authentication and authorization are logically distinct flows, and consequently it is generally preferable for them to be technically decoupled as well.
This was not the case for HTTP services for quite some time, prior to the advent of OAuth.
Some examples of tightly coupled AuthN/AuthZ:
HTTP Basic Auth: adding the
Authorizationheader as Base64-encoded “username:password”API keys: static, long-lived keys passed in the HTTP header
x-api-keyorAuthorization: Bearer {key}Cookies: server sets a session cookie after authentication that binds the user’s session to their authentication status.
This is not to say all such couplings are inherently “bad”.
Cookies work well for same-origin web applications. However when the scope includes third-party integrations or mobile clients such coupling introduces problems.
Kerberos is designed to tightly couple authentication and authorization as well but is a fairly robust protocol when dealing with enterprise auth management. (It even back Microsoft Entra ID’s directory technology, from what I understand, to this day.)
But with the rise of third-party API integrations came a distinct need to decouple authentication and authorization.
Hence, OAuth.
OAuth 1.0: A First Stab
Web developers came together to find a solution that would abstract the authorization process out of authentication. OAuth 1.0 was originally published in October 2007 to solve this problem with RFC 5849 introduced in April 2010.
Although it came to be replaced fairly quickly with OAuth 2.0, this first OAuth protocol did lay a lot of the groundwork for its successor protocol.
As noted in RFC 5849, the traditional client-server authentication model depends on the client using its credentials to access server-hosted resources. However, the rise of cloud and distributed systems introduced requirements for third-party access at scale.
OAuth attempted to solve this by adding a third role to the client and server model: the resource owner. In this new paradigm, a server may host resources that it may not necessarily own. Consequently, the client can request access from the resource owner and then proceed to where those resources are hosted to be provided access, all while verifying the identity of the requesting client.
It should be remembered that OAuth is purely an authorization protocol, as it can be too easy to mentally slip it up and consider it at least partially authentication-based.
Delving into the inner working of OAuth 1.0 may be a useful exercise but is not our intent here as it is largely obsolete. There are several reasons why OAuth 1.0 needed to be replaced by a successor within a short timeframe.
In brief, OAuth 1.0 suffered from these flaws:
It required message-level security with signature-based verification. This is not intensive but also not layer properly with transport layer security technologies such as TLS or JWTs.
Every API request needed to be signed using either HMAC-SHA1 or RSA which is not only difficult to implement but can understandably introduce performance degradations.
Mobile clients and SPA’s cannot securely integrate with a signature-based flow
Low extensibility made it difficult to add token formats, transport mechanisms, or grant types, which is not ideal for a critical fast-moving technology.
OAuth 2.0 Fundamentals
Lessons were learned and the developer community came up with a fully reimagined authorization protocol to replace OAuth 1.0.
RFC 6749 emerged in October 2012, defining and laying the groundwork for what remains to this day one of the most widespread authorization protocols in digital technology.
While OAuth 1.0 defined three roles of client, server, and resource owner, this was further expanded to four roles in OAuth 2.0 defined in this way
Client: The application making requests for protected resources on authorized behalf of the resource owner
Resource Owner: The end-user or entity capable of granting access to a protected resource.
Authorization Server: The server that issues access tokens to the client authorized by the resource owner when that resource owner has been authenticated.
Resource Server: The server hosting the protected resources, responding to resource requests carrying valid access tokens.
Client Registration
Before using OAuth 2.0, clients must register with the authorization server. Registration requirements vary by implementation but typically include:
Client type (confidential or public)
Redirect URIs
Additional server-specific information
Clients are categorized as:
Confidential: Can securely store credentials (e.g. server-side applications)
Public: Cannot keep credentials confidential (e.g. SPAs, mobile apps)
Client Authentication
Confidential clients or clients with credentials must authenticate with the authorization server when making requests to the token endpoint. This authentication serves several important purposes:
Enforces the binding of refresh tokens and authorization codes to the correct client
Helps a compromised client recover by invalidating previous credentials
Implements periodic credential rotation for enhanced security
The method of authentication can vary depending on the implementation, but typically involves the client ID and client secret.
OAuth 2.0 Endpoints
The OAuth 2.0 protocol utilizes three primary endpoints:
Authorization Endpoint: Used for resource owner interaction and authorization grant issuance
Returns
codeparameter for Authorization Code flowReturns
tokenparameter for Implicit flowMust authenticate the resource owner before granting authorization
Token Endpoint: Where clients exchange authorization grants for access tokens
Requires client authentication for confidential clients
Issues access tokens and optional refresh tokens
Redirect Endpoint: The client's endpoint where the authorization server redirects after authorization
Must be registered during client setup
Should be protected by TLS
OAuth 2.0 Tokens
Before diving into the grant types, it's important to understand the tokens used in OAuth 2.0:
Access Tokens
Credentials used to access protected resources
Represent specific authorization scopes and durations
Presented to resource servers when making API requests
Refresh Tokens
Used to obtain new access tokens when the current ones expire
Never sent to resource servers
Provide a way to maintain long-term access without requiring re-authentication
OAuth 2.0 Authorization Grant Types
1. Authorization Code Grant
The Authorization Code grant is a user-based flow designed for server-side applications that can securely store client secrets.
Flow:
The client redirects the resource owner to the authorization server via the user's browser
The authorization server authenticates the resource owner
The authorization server redirects back to the client with an authorization code
The client exchanges this code for an access token through a secure back-channel request
Authorization Request Parameters:
response_type: Must be "code"client_id: Client identifierredirect_uri: (Optional) Where to send the responsescope: (Optional) Requested access scopestate: Recommended parameter to prevent CSRF attacks
Authorization Response:
code: The authorization code that will be used to request an access tokenstate: Same value as in the request (if included)
Access Token Request Parameters:
grant_type: Must be "authorization_code"code: The authorization code receivedredirect_uri: Must match the original request URI (if included initially)client_id: Required if client does not authenticate
Access Token Response:
access_token: The access tokentoken_type: Type of token (usually "Bearer")expires_in: Token lifetime in seconds (optional)refresh_token: Token to get a new access token (optional)scope: Scope of access token (optional)
This grant flow has several advantages including the ability to use refresh tokens so that the user does not need to reauthenticate, the resource owner’s credentials are never exposed to the client, and access tokens are passed directly to the client without required exposure to other entities. This is one of the most secure OAuth 2.0 flows.
2. Implicit Grant
The Implicit grant is a simplified version of the Authorization Code flow, primarily designed for single-page applications (SPAs) and mobile clients that cannot make secure back-channel calls.
Flow:
The client redirects the user to the authorization endpoint
After authentication, the authorization server returns the access token directly in the URL fragment
The client application extracts the token from the URL
Authorization Request Parameters:
response_type: Must be "token"client_id: Client identifierredirect_uri: (Optional) Where to send the responsescope: (Optional) Requested access scopestate: Recommended parameter to prevent CSRF attacks
Authorization Response (in URL fragment):
access_token: The access tokentoken_type: Type of token (usually "Bearer")expires_in: Token lifetime in seconds (optional)scope: Scope of access token (optional)state: Same value as in the request (if included)
In contrast to Authorization Code, the Implicit flow does not allow the use of refresh tokens for security reasons. Furthermore, it is less secure as the tokens are exposed in browser URL fragments which can then be stored in browser history or accessible to any JavaScript running in the browser. It still has the advantage of skpipng the authorization code exchange and thus saving a round trip, but is relatively insecure.
Today, the Implicit flow has largely been deprecated in favor of Auth Code with PKCE (more on this below).
3. Resource Owner Password Credentials Grant (ROPC)
The ROPC grant is designed for scenarios where there is a high degree of trust between the resource owner and client.
Flow:
The client directly collects the resource owner's username and password
The client sends these credentials to the authorization server
The server validates and returns an access token
Access Token Request Parameters:
grant_type: Must be "password"username: Resource owner's usernamepassword: Resource owner's passwordscope: (Optional) Requested access scope
Access Token Response:
access_token: The access tokentoken_type: Type of token (usually "Bearer")expires_in: Token lifetime in seconds (optional)refresh_token: Token to get a new access token (optional)scope: Scope of access token (optional)
This flow is a strange one, primarily because it reintroduces the authentication and authorization coupling that OAuth 2.0 was designed to separate. It does allow some use of refresh tokens but does not have many advantages. It is altogether discouraged unless absolutely necessary.
4. Client Credentials Grant
The Client Credentials grant is designed for userless auth journeys between various servers.
Flow:
The client authenticates with its own credentials (client ID and secret)
The authorization server validates these credentials and issues an access token
The client uses this token to access protected resources
Access Token Request Parameters:
grant_type: Must be "client_credentials"scope: (Optional) Requested access scope
Access Token Response:
access_token: The access tokentoken_type: Type of token (usually "Bearer")expires_in: Token lifetime in seconds (optional)scope: Scope of access token (optional)
With this flow, the machine identity can simply reauthenticate automatically and so refresh tokens are not generally used, except perhaps for performance reasons. This is very common implementation for API communication and microservice-driven environments which rely upon service accounts.
RFC 6749 specified those four OAuth 2.0 authorization grant flow types, but there remained room for more.
Several of which have gained widespread adoption in the years since then.
5. Authorization Code with PKCE
How can public clients such as mobile apps or SPA participate in OAuth 2.0? Authorization Code grant is susceptible to interception attacks while the Implicit flow bypasses authorization grants altogether.
Proof Key for Code Exchange by OAuth Public Clients (RFC 7636) was designed to solve just this problem.
PKCE introduces a new factor to the OAuth equation: the code_verifier.
Essentially, a unique code verifier value is generated by the client for each authorization request. It is then transformed typically using SHA-256 into a value known as the code_challenge and sent to the authorization server to fetch an authorization code.
The authorization code is returned to the client. At this point, the client sends an access token request with the authorization code to the token endpoint with the code_verifier to the token endpoint which then returns the access token. Thus, an intercepted authorization code cannot be used without the accompanying code_verifier.
Authorization Request Parameters:
All standard Authorization Code parameters
code_challenge: The transformed code verifiercode_challenge_method: The method used to transform the code verifier (e.g., "S256" for SHA-256)
Access Token Request Parameters:
All standard Authorization Code token request parameters
code_verifier: The original code verifier string
Both the Access Token response and the Refresh Tokens follow the same standard format as Authorization Code.
This is the recommended flow for all OAuth clients including both public and confidential clients by mitigating the risk of browser redirect theft or network interception.
6. Device Authorization Grant
The Internet has expanded beyond computers and smartphones to a wide panoply of “Things”. What does auth work like in the world of IoT where devices may not have browsers?
Device Authorization Grant (RFC 8628) answers just such a question, at least for Internet-connected devices that can make HTTPS requests.
This flow follows these steps:
The client sends an access request with a client identifier to the authorization server.
The authorization server issues a device code and an end-user code with an end-user verification URI.
The client instructs the end user (resource owner) to visit the verification URI on another device (transferring their user agent) and provides them with an end-user code.
The end user visits the verification URI, authenticates with the authorization server, then provides their user code. The authorization server will validate the user code and have the end user confirm the device auth request.
The device client will poll the authorization server to determine if the endu user has confirmed the auth request. Once this is completed, the polling response will include the access token.
Those with smart TV’s may already be familiar with this auth flow as one common consumer-facing implementation flow.
Or for those working with cloud providers, you may recognize this flow as a method for authenticating your terminal CLI (headless) against a cloud provider.
The key innovation in this grant flow is the device authorization endpoint which is entirely separate from the authorization endpoint reached by users.
Device Authorization Request Parameters:
client_id: Client identifierscope: (Optional) Requested access scope
Device Authorization Response:
device_code: Code used by the client to poll for the access tokenuser_code: Code displayed to the user, typically easy to typeverification_uri: URI the user should visit to authenticateverification_uri_complete: URI with the user code embedded (optional)expires_in: Lifetime of the device code in secondsinterval: Minimum time in seconds between polling requests (optional)
Token Request Parameters:
grant_type: Must be "urn:ietf:params:oauth:grant-type"device_code: Device code received in the authorization responseclient_id: Client identifier (if client authentication is not used)
Refresh tokens are almost a necessity in this flow from a UX perspective because these device s maintain long-term access and the inconvenience of reauthenticating IoT devices is not appealing to most end-users.
This is a very robust pattern for extending the OAuth 2.0 framework out into the world of IoT and headless device auth (e.g. CLI).
7. Token Exchange Grant
Access tokens are useful technology when implemented properly. A credential with clearly-defined boundaries, expiration, that can be revoked remotely as needed.
But an access token on its own is immutable. If an authenticated user with broad authorization wants to delegate this access token to another user or system that should have fairly limited scope, there is little that can be done without reauthenticating through some other possible authorization flow.
Token Exchange Grant (RFC 8693) was the next extension to OAuth 2.0 to solve this problem by allowing for token exchanges for the purpose of delegation, authorized impersonation, token descoping, or service chaining.
This flow is simple enough. The client presents an existing token to the token endpoint with specifications for the new token. If these are valid, the authorization server will validate the request and issue a new token in the response. Simple as that.
Access Token Request Parameters:
grant_type: Must be "urn:ietf:params:oauth:grant-type"subject_token: The token representing the subjectsubject_token_type: The type of the subject tokenactor_token: (Optional) Token representing the actoractor_token_type: (Optional) Type of the actor tokenresource: (Optional) Identifier for the target service or resourceaudience: (Optional) Logical name of the target servicescope: (Optional) Requested scoperequested_token_type: (Optional) Type of token requested
Access Token Response:
access_token: The issued tokenissued_token_type: Type of the issued tokentoken_type: Type of the access tokenexpires_in: Lifetime in seconds (optional)scope: Scope of the issued token (optional)refresh_token: Token to get a new access token (optional)
Assertions
Assertions, in the context of auth, are not original to OAuth 2.0. SAML and WS-Federation had been using assertions long before OAuth hit the scene.
An assertion is a set of data that allows one entity to make claims about an identity or subject. It functions as a digitally signed statement so that a valid assertion can be taken as fact by the designated recipient (audience) that the identity or subject has the attributes the issuer reports.
8. Assertion Grants
RFC 7521 opened the door for defining how assertions could be used as authorization grants within OAuth 2.0 both for SAML (RFC 7522) and JWT (RFC 7523)
At its highest level, assertion grant flows follow this sequence:
A client obtains an assertion from the issue
The client presents the assertion to the token endpoint
The authorization server validates the assertion according to type-specific rules
If valid, the authorization issues an access token to the client.
Broadly speaking, SAML is more commonly adopted in enterprise and legacy environments, particularly for web based SSO, while JWT is much more compact and widely embraced in modern web and mobile applications as well as API-centered ecosystems.
We won’t go into the SAML side here but let’s give a little more information on JWT.
JSON Web Tokens
JWT (JSON Web Token) emerged around the same time, though independently of, OAuth 2.0 and was standardized in RFC 7519 published in May 2015. Although it was not built from OAuth it did explicitly allocate a subname registration for OAuth (cf. 10.2), so the two were meant to go hand-in-hand.
XML in all its bulk and unreadability was an industry standard for quite some time, and as JSON continued to grow in popularity as an interchange format, so too did the desire for a lightweight token format based on JSON, especially as stateless APIs came to fruition.
JWT accomplishes several things at once which are important for OAuth:
It provides a compact way to encode token information (HTTP headers)
Tokens can directly include claims, no external reference required.
Built-in signature verification to ensure token integrity
All of these are contained in its simple tripartite format of a header which identifies the signature’s algorithm, the payload which contains the claims (assertions about the subject), and a signature to ensure token integrity.
JWT is not just limited to OAuth but has taken wide and deep root across the entire IAM and auth world.
In the context of OAuth 2.0 though it takes this format.
JWT Access Token Request Parameters:
grant_type: Must be "urn:ietf:params:oauth:grant-type"assertion: The JWT assertionscope: (Optional) Requested access scope
Access Token Response: Standard OAuth 2.0 token response.
JWT Required Claims:
iss: Issuer - who issued the JWTsub: Subject - who the JWT is aboutaud: Audience - recipient(s) the JWT is intended forexp: Expiration timeiat: Issued at timejti: JWT ID - unique identifier for the token
OAuth 2.0: Best Security Practices
RFC 9700 came out fairly recently when this was written, and it provides a general collation of security vulnerabilities and best practices around the OAuth 2.0 framework.
A suitable way to close this breakdown is to visit these threat vectors and best practices.
OAuth 2.0 Attack Vectors
This RFC publication lays out a number of attacker profiles which are specifically targeted by the mitigation document:
A1: An attacker who operates OAuth clients registered with the targeted authorization server, luring users to their compromised URIs.
A2: Network attackers who can eavesdrop, manipulate, block, or spoof messages that are not protected by TLS.
A3: Attackers that can read authorization response content
A4: Attackers that can read authorization request content
A5: Attackers that can acquire a valid access token issued by the authorization server
From these attacker profiles come the following attack types:
1. Insufficient Redirection URI Validation
Threat Vector: Redirection URI patterns introduce risk as opposed to complete, literal URI redirect matching.
If subdomains or paths have wildcard or other regex matching characters, it is possible for an attacker to hijack that subdomain or path, and then steal authorization codes or access tokens.
Mitigation: Use exact redirection URI matching.
Additional steps:
Do not expose open redirectors
Attach an arbitrary fragment identifier
#_to the redirection URI to prevent browser from reattaching them to Location (insecure)
2. Credential Leakage via Referer Headers
Threat Vector: Authorization codes or state values can be unintentionally disclosed in the Referer HTTP Header. Disclosed state values remove protection from CSRF attacks.
Mitigation: The page rendered as the result of the OAuth authorization response should not include third-party resources or links to external sites.
Additional steps:
Suppress the
Refererheader through a policyBind the authorization code to a confidential client or PKCE challenge.
Authorization codes must be invalidated by the authorization server after their first use at the token endpoint.
State value should be invalidated by the client after its first use at the redirection endpoint.
Use the form post response mode instead of a redirect for the authorization response.
3. Credential Leakage via Browser History
Threat Vector: Authorization codes and access tokens can end up in the browser’s history of visited URLs (e.g. Implicit Flow) which when exposed can lead to replay attacks.
Mitigation: Authorization code replay prevention and prevent access tokens from being included in URI query parameters
4. Mix-Up Attacks
Threat Vector: An OAuth client interacts with multiple authorization servers, one of which is under the control of an attacker. The attacker tricks the client into sending authorization codes or access tokens to the wrong server, thus hijacking them.
This can happen if the client stores the compromised authorization server in their session, redirects the user to that compromised auth server, then the compromised auth server forwards the request to a legitimate authorization server, using that legitimate server’s client ID. The user will log in on the legitimate server, but that server would then send the authorization code to the attacker.
Variants of this include intercepting and modifying the user’s traffic, intercept the access token in implicit grant, per-authorization server redirect URI’s, or OpenID Connect Abuse
Mitigation: Every auth response should include the iss issuer ID as a parameter or claim.
5. Authorization Code Injection
Threat Vector: An attacker who has acquired a valid authorization code may try to redeem that authorization code for an access token by injecting it into their client, attaching the victim’s identity to the attackers session.
Mitigation: PKCE mitigates this, attaching and checking a random nonce value when requesting the session can prevent this as well.
6. Access Token Injection
Threat Vector: An attacker with a valid, stolen access token may inject this into a legitimate client.
Mitigation: OAuth 2.0 on its own cannot mitigate this but integration with OpenID Connect could include the at_hash claim in the ID token, introducing an additional layer of protection.
7. Cross-Site Request Forgery
Threat Vector: An attacker injects to a legitimate client’s redirection URI to cause hte client to access resources under the attacker’s control. This is known as CSRF.
Mitigation: Linking the request to the user agent session is a sufficient countermeasure and can be implemented via including state or nonce values or using PKCE.
8. PKCE Downgrade Attack
Threat Vector: If an attacker can disable PKCE in their authorization request, PKCE can be downgraded so that state is not used at all, leaving OAuth open to CSRF.
Mitigation: It cannot be guaranteed that OAuth clients will universally implement state properly, so authorization servers must reject any code_verifier token requests if there were no code_challenge authorization requests.
9. Access Token Leakage at the Resource Server
Threat Vector: Counterfeit or compromised resource servers can receive access token and use the authorization granted in that token to target other resource servers.
Mitigation: Sender-constrained access tokens to prevent access replay attacks as well as audience restriction (see next one for more detail).
10. Misuse of Stolen Access Tokens
Threat Vector: Access tokens can be stolen in various ways and then used to access other resource servers.
Mitigation: Two primary strategies:
Sender-constrained Access Tokens: The sender is obligated to demonstrate knowledge of a certain secret as a pre-requisite for a resource server to accept the sent token. This can be implemented by either Mutual-TLS Client Authentication and Certificate-Bound Access Tokens (RFC 8705) or Demonstrating Proof of Possession (RFC 9449).
Audience-Restricted Access Tokens: Audience restriction
audscopes a token to a defined resource server. This also can enable token format to be custom tailored for various audiences. Implementation mechanisms are documented in RFC 6749 3.3 and RFC 8707
11. Open Redirection
Threat Vector: Open redirection at the client or authorization server level can introduce phishing or malicious redirects.
Mitigation: On the client side, only redirect if target URL’s are allowed or request integrity can be validated (OWASP guidelines). On the authorization server side, the client must always be authenticated before redirection.
12. 307 Redirect
Threat Vector: If an HTTP 307 (Temporary Redirect) occurs at the OAuth auhtorization endpoint, this can leak user credentials to the client (which in most authorization grant flows should not be happening).
Mitigation: HTTP 303 (See Other) is a better status code in this case because it converts POST to GET and drops the user credentials.
HTTP 302 is another option but can have more variable behavior depending on the browser.
13. TLS Terminating Reverse Proxies
Threat Vector: If a reverse proxy is deployed in front of the application server and TLS traffic terminates at the proxy, an attacker could try to circumvent security controls by manipulating particular HTTP headers such as X-Forwarded-For and then gaining the ability to eavesdrop, inject, or replay messages.
Mitigation: Reverse proxies must sanitize inbound requests to ensure the authenticity and integrity of all header values relevant to application server security.
14. Refresh Token Protection
Threat Vector: Refresh tokens grant full access to mint fresh access tokens. They can be stolen and replayed to gain access tokens.
Mitigation: Documented in RFC 6749. Use TLS in transit. Ensure tokens are confidential, unique, and non-guessable. Bind them to a specific client and leverage expiration, revocation, and rotation.
15. Client Impersonating Resource Owner
Threat Vector: A resource server could mistake a client for a resource owner such as when a malicious client could set a client_id to a value identifying the resource owner instead, whereupon the authorization server may accidentally believe the client is the resource owner.
Mitigation: Authorization servers should not allow clients to influence client_id, or if this is not possible, enforce another mechanism to distinguish between access tokens issued by client credentials and those issued by user-based authorization grant flows.
16. Clickjacking
Threat Vector: Clickjacking (i.e. UI redressing) can trick a user to accidentally click on a hidden link that can redirect authorization and cause credential theft or scope manipulation.
Mitigation: Authorization servers are responsible for preventing clickjacking by using CSP Level 2+ and X-Frame-Options headers. CSP is preferable for flexibility and multiple allowed origins.
17. Attacks on In-Browser Communication Flows
Threat Vector: When OAuth uses postMessage (example) instead of a redirect, which is common in SPAs, this can cause origin risks, with cross-site attacks
Mitigation: Authorization servers must use exact string matching for receiver origins and should only send to trusted origins. Clients must reject messages from untrusted origins using strict e.origin checks. The idea is to extend all redirect-based protections to postMessage flows.
Nutshell: 15 Best OAuth 2.0 Security Practices
Combining all these various attack vectors and mitigations, here are 15 best practices highlighted in RFC 9700 to ensure tight security on OAuth flows.
Redirect URIs should have exact string matching.
Avoid open redirectors.
Enforce HTTPS.
Never leak credentials during HTTP 307. (Use HTTP 303).
CSRF protection: PKCE for public clients,
noncefor Open ID Connect, or state-bound CSRF tokens otherwise.Only use secure and transaction-specific PKCE challenge methods (e.g. SHA-256)
If multiple authorization servers, use the
issparameter or secondarily distinct redirect URIs per server.Never use Implicit grant. For public clients, use PKCE.
Never use Resource Owner Password Credentials (ROPC) Grant.
Use sender-constrained access and refresh tokens (via Mutual TLS or DPoP) where possible.
Use token scoping, audience restriction, and fine-grained access.
Use asymmetric client authentication.
Use authorization server metadata to auto-discover endpoints and enable secure client auto-configuration.
Verify
postMessageorigins.Do not support CORS on the authorization endpoints.
Conclusion
OAuth 2.0 has a plethora of practitioners, but only a subset of those individuals often conceptually grasp how the framework’s esoteric (and perhaps clunky) words connect to each other.
Despite its relative complexity, OAuth 2.0 is certainly one of the most robust authorization frameworks to have ever been developed, and it behooves even the junior engineer to learn its general ins-and-outs as part of their journey as a technologist.
Understanding these authorization flows not only strengthens how security is implemented but can also open the door for extensibility as more sophisticated, secure, and user-friendly auth experiences continue to be engineered.
