OAuth Client Credentials Flow vs. Authorization Code Flow with PKCE: Which One to Use and When

OAuth Client Credentials Flow vs. Authorization Code Flow with PKCE: Which One to Use and When
Photo by Rabah Al Shammary / Unsplash

Building your own authentication and authorization system is extremely time-consuming and complex. OAuth2 and OIDC (OpenID Connect) are reliable, widely used protocols that securely manage token-based authentication and authorization. OAuth2 enables a client to access an API on behalf of the client app or its user. OIDC, on the other hand, ensures the user's identity is verified to the client app. By using OAuth2 and OIDC, you can focus on building your app's core features while relying on these protocols to securely manage authentication and authorization. Duende IdentityServer is one such example of an identity provider that implements the OAuth2 and OIDC standards. It manages tasks such as token handling and user identity management. We can therefore focus on building our app's core features while delegating authentication and authorization to Duende. How can a client app achieve authorization with OAuth2 and authentication with OIDC? OAuth2 and OIDC offer different flows that describe how an app interacts with an authorization server to get access tokens and, with OIDC, user identity information. The choice of flow depends on your specific use case and security needs. In this post, we'll explore two of the most widely used flows:

  • Client Credentials Flow
  • Authorization Code Flow with PKCE (Proof Key for Code Exchange)

We'll briefly cover how each flow works and when it's best to use them.

Client Credentials Flow

The Client Credentials flow is a server-to-server process that doesn't involve a user. It allows an application (client) to access resources or perform actions on its own behalf, rather than on behalf of a user. For example, a payment service (client app) can use Client Credentials flow to prove its identity and access a payment API to process payments, all without needing a user.

Suppose a client app needs to get data from a protected API. The flow steps are:

  1. The client app must be registered at the level of the identity provider
  2. The client app sends a request with its Client ID and Client Secret to the identity provider’s token endpoint
  3. The identity provider validates the credentials and if valid issues an access token
  4. The client uses the token to make a HTTP request to the API, adding it as a bearer token in the header of the request
  5. The API validates the token and grants access if valid

The Client Credentials flow is used when user involvement is not required, and the application needs to access resources or perform actions on its own behalf.

Authorization Code Flow with PKCE

The Authorization Code Flow with PKCE is best for public clients (e.g. mobile, single-page and JavaScript apps) that can't securely store sensitive data, such as client secrets. Using PKCE helps protect the authorization code from being intercepted. For example, in the standard Authorization Code Flow (without PKCE), the authorization code is passed through the user's browser, which makes it vulnerable to interception by attackers who could use it to obtain an access token. PKCE adds an extra layer of security with a code verifier and challenge, ensuring that an attacker cannot exchange the authorization code for an access token without the correct code verifier.

The flow steps are:

  1. The client app generates a random string known as the Code Verifier. This Code Verifier is then hashed using a function such as SHA256 to create the Code Challenge.
  2. Client app makes an authentication request to the identity provider's authorize endpoint. The request includes the Code Challenge. The identity provider stores the Code Challenge for later use.
  3. The user is prompted to authenticate and grant the necessary permissions to the client app. Once the user successfully authenticates and consents, the authorization server sends an Authorization Code back to the client app.
  4. Client app then calls the identity provider's token endpoint. The request includes the Authorization Code, Client ID, Client Secret (for confidential clients), and the Code Verifier generated in Step 1.
  5. The identity provider hashes the Code Verifier and checks if it matches the previously stored Code Challenge. If they match, the verification process is successful.
  6. If the Code Verifier matches the stored Code Challenge, the identity provider returns an access token (and optionally a refresh token), along with an ID token if OIDC is being used.
  7. The client app can now use the access token to access protected resources (APIs). If OIDC is being used, the client can also use the ID token to authenticate the user.