Executive Summary — OAuth 2.1
OAuth provides a standard for delegated authorization by granting consent to an application (e.g. Yelp) for access to resources (e.g. user contacts) in another application (e.g. Gmail).
For example, suppose you have a Yelp account and a Google account. You need to import your contacts from your Google account into your Yelp account. Yelp must gain access to these contacts (resources) stored by Google.
Perhaps Yelp could prompt for your Google credentials, then use these credentials to call a Google API directly. That could work, but it is clearly insecure. We do not want Yelp or any other application to know our Google credentials. This solution also does not scale in a world where there are communication channels across many 3rd parties. Eventually every application would need to know every other application’s credentials.
Instead, we use an OAuth flow. Here, Yelp is securely granted access to a finite scope of resources owned by the Google account. In the OAuth flow, Yelp simply redirects you to the Google login page where you provide your Google credentials (to Google). Google then redirects back to the Yelp app/site with a temporary token defining the parameters of temporary access which have been granted. Now, Yelp may use this token to retrieve the resources.
This is very exciting because Google has verified that you are indeed the human using the Yelp application. And it permits Yelp to get access to your personal contacts stored in Google’s ecosystem. All without explicitly telling Yelp who you are or what your Google credentials are.
Even when 3rd party applications are not involved, where all of your applications are owned by the same entity who owns the user credentials, OAuth remains valuable. The reason is because it centralizes authentication outside of any single application. OAuth offloads the responsibility for prompting for user credentials from each application. For example, Google has a dedicated login screen. There is not a unique login screen for Docs, Gmail, Calendar, and so on. This is good modular system design.
Goals of the OAuth Standard
- Allow 3rd party applications to access user data in your system.
- Provide a modular architecture in which the authorization service is separated from the application services. Note: This makes changes to authorization easier to roll out. For example, adding MFA into the login flow can be done in one place rather than in each app.
- Centralized password protections and compliance with local data sharing laws.
- Alleviate applications from having to see or store user credentials directly.
- Ensure that user consent occurs — that the user is active when logging in, and is not some automated process logging in.
While this, so far, may suffice as an executive summary, I will continue with more details of OAuth. If the internals of OAuth are unimportant to you, there is no need to continue reading.
Roles defined by the OAuth Standard
OAuth exists where there is communication across many components. It’s important to explicitly define each player in the game.
- Resource Owner (the user). In the example above, you are the resource owner of your Google contacts.
- User-Agent (the device). In the example above, the user-agent would be the browser, like Chrome or Firefox.
- OAuth Client (the application). In the example above, Yelp is the OAuth client.
- Resource Server (an API). In the example above, Google is the resource server.
- Authorization Server (the OAuth Server). In the example above, Google is also the OAuth server. This may also be referred to as the Identity Provider, or IdP.
Flows Defined by the OAuth Standard
OAuth may be implemented in several ways depending on the software system involved. Each implementation is called a flow (or a grant type). The first two flows listed here are simplistic legacy flows which I note only for context.
Each flow assumes that the client application has previously registered with the OAuth server. As noted below, registration will generate a client_id and a client_secret.
(I will use terminology yet to be defined here. See subsequent sections for terminology.)
- Password Flow — Here, the application itself presents a password prompt . The application then accepts credentials and sends them to the auth server. For a 1st party application this is OK, yet not ideal because we cannot be sure that the user has consented. For a login to a 3rd party application, this is very bad, as noted in the original example above. A 3rd party application must not ask for credentials to another account.
- Implicit Flow — Here, there is no back channel communication. Access Token is obtained in the front channel, insecurely (see below). Because CORS is now widely available in browsers, this is no longer necessary.
- Authorization Code Flow — This replaces the Password Flow. First, the application redirects users to the Authorization Server UI (/auth endpoint), which provides an authorization_code to a redirect URL in the application. This code is then sent in the back channel to the Authorization server’s /token endpoint to obtain the final access_token (and perhaps a refresh_token). The access_token may then be used to authenticate API calls.
- Authorization Code + PKCE Flow — Same as above but used more likely in the SPA and mobile application cases.
- Refresh Token Flow — This is simply a subsequent flow to the Authorization Code flow. See Refresh Token below.
- Client Credentials Flow— This can be used in server to server communication. No human involved. No consent required. No redirects. May include the client_secret if it is a confidential application type. This is used in machine to machine communications when no human is involved.
- IoT Flow — Beyond our scope.
Here is an abstracted diagram of the Authorization Code flow. Details have been removed in favor of clarity.
- Confidential Client: can be deployed with a client secret. For example, a back end service which is not run on external browsers, devices, or computers.
- Public Client: cannot be deployed with a client secret.
- Front Channel communication: Literally, this is an HTTP/S GET with a query string in the address bar of the browser. This is an insecure communication channel. It must not include sensitive data.
- Back Channel communication: Literally connecting to an HTTPS endpoint but not necessarily server to server. Requests can originate from a browser or an app as well. This is considered a secure communication.
- Local Token introspection: This occurs when an application decodes, verifies, then parses a JWT. This is a fast way to verify a token as it does not require repetitive network I/O.
- Remote Token Introspection: Always gets the latest state of user credentials on the server. This incurs a high network cost. Often the JWT will be a reference token, which includes some key in a remote DB, for example. Rather than an entire state of a user’s account
- PKCE (Proof Key for Code Exchange, aka Pixie): This is a recent addition to OAuth which avoids a security gap. With PKCE, client generates a random string, then hashes it, which is called the Code Challenge. This is not a core component of OAuth flow.
OAuth Message Attributes
- Client_id: the application identifier, which is generated when the application is registered with the OAuth server.
- Client_secret: the actual application password, which is generated when the application is registered with the OAuth server. This must not be used for mobile applications or SPAs, because it would be visible to the public. The client_secret must remain private and must never be shared with others.
- Redirect_URI: the application URL provided to the OAuth server upon registration of the application. The OAuth server will redirect back to this URL once the user logs.
- Authorization_code: a random string provided to the application from the auth server. Typically, this is provided in the front channel and is thus insecure. It will typically expire within one minute.
- Access_token: usually, this is a JWT provided to the application then the API from the auth server originally.
- Id_token: a JWT. Used only in OpenID Connect calls.
- Refresh_token: a random token whose single purpose is to get new access_token. Refresh tokens allow access tokens to expire frequently without requiring the user to, again, provide consent.
- Scope: The resource which the client is requesting access to on the resource server. E.g. photos:read, photos:create.
Note on OpenID Connect: This is simply another scope for OAuth. scope=openid. Returns an id_token (JWT) with user information in it such as email, name, last login, picture, and anything else.