Blog

Understanding the OAuth 2.0 Client Credentials flow

Learn how to use OAuth for secure machine-to-machine communication with the Client Credentials flow.


The OAuth 2.0 framework supports different flows (or grants) depending on the type of application requesting access. One of these is the Client Credentials flow, which is used for machine-to-machine (M2M) communication.

The Client Credentials flow can be used by any system that can communicate over a network: a physical server, an IoT device, a backend service, a CLI, a script, or an AI agent. For example, an airline server could use the Client Credentials flow to retrieve a list of flight bookings from another service. The only constraint is that the client running the flow should be able to hold secrets safely.

What differentiates this OAuth flow from the rest is that there is no user to delegate access. The flow provides a secure way for applications to access resources on their own behalf rather than on behalf of a user.

In this article, we will discuss the Client Credentials flow, how it works, and how you can use it.

What is the Client Credentials flow?

The Client Credentials flow is a specific OAuth 2.0 authorization flow used when a client application needs to authenticate itself (i.e., prove its identity) and obtain an access token to interact with a server or API. Unlike other OAuth flows, such as the Authorization Code, which are designed for user-based authorization, the Client Credentials flow is primarily used for machine-to-machine (M2M) authentication, where no user interaction is involved.

In this flow, the client application does not request access to any user-specific data; instead, it requests access to its own resources.

This flow is commonly used for communication between microservices within the same ecosystem, server-to-server communication, daemons or CLIs that need to access internal or external services or APIs, wearables, and more.

How the Client Credentials flow works

The Client Credentials is the simplest OAuth flow.

First, the client application registers with an Authorization Server (i.e., a server responsible for authenticating clients and issuing tokens, like WorkOS) and gets issued a Client ID and Client Secret:

  • The Client ID is the public identifier of the app (i.e., the app’s username).
  • The Client Secret is the private key used to authenticate the app (i.e., the app’s password).

As the name implies, the Client Secret is confidential information and should be stored safely. Since the application runs on the backend, it can be trusted with secrets. This process happens only once.

After that, when the app wants to access resources from another service or API, it authenticates with the Authorization Server using its Client ID/Secret and gets an access token in return. The app can then use that access token to access the other service or API, until the token expires (whereupon a new one must be obtained).

Here’s a step-by-step breakdown of the flow:

  1. The client application sends a request to the OAuth Authorization Server's token endpoint. The request includes:
    1. grant_type: This must be set to client_credentials, indicating that the client is using the Client Credentials flow.
    2. client_id: The client’s unique identifier issued by the Authorization Server.
    3. client_secret: The client’s secret key, used to authenticate the client.
    4. Optionally, scope: A parameter that specifies the level of access or the specific resources the client needs to access. Not all implementations require the scope, but it can be used to limit access to specific APIs.
  2. The Authorization Server validates the application’s credentials. If they are valid, the server generates an access token - a JWT containing a JSON payload. This JWT contains the claims – pieces of information about the token:
    1. iss: who issued the token
    2. sub: who the token was issued for
    3. aud: who is the intended recipient of the token
    4. exp: when the token expires
    5. scopes (optionally): what actions should be allowed to the bearer of this token
  3. The Authorization Server responds with the access token. This token is a bearer token, which the app can use to make authorized requests to the service it wants to access.
  4. The client application must always validate the token. JWTs should never be implicitly trusted, even if the whole flow runs within an internal network without internet access. Verifying and validating a JWT includes the following steps.
    1. Parse the JWT to extract the header, payload, and signature.
    2. Verify the signature using the secret key or public key.
    3. Check the expiration time (exp) and the not-before time (nbf) claims to ensure the JWT is valid.
    4. Verify the issuer (iss) claim to ensure the JWT was issued by a trusted party.
    5. Verify the audience (aud) claim to ensure the JWT is intended for the correct recipient.
  5. Once the client validates the access token, it can use it to make authorized requests to the intended server or API. The access token is included in the HTTP request header as a bearer token.
  6. The server receives the token, validates it (the same way that the app validated it in the previous step), and grants access to the requested resource, provided that the token is valid and not expired.

How to use the Client Credentials flow

Let’s see how you can implement the Client Credentials OAuth 2.0 flow.

First, you need to register your app with an Authorization Server. This will provide you with a Client ID and Client Secret.

Once you have your credentials, the client application can request a token, using client_credentials as the grant_type and sending over its Client ID and Client Secret.

This is what this request looks like:

  
POST /oauth/token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
scope=YOUR_SCOPES
  

After the Authorization Server, validates the app’s credentials, it generates and returns a token. Some other info is also returned like the type of the token (usually bearer) and the number of seconds until the token expires. After this period, the client will need to request a new access token.

Note that if the app requested specific scopes and not all of them were granted, then the response will include also a scope property with the granted scopes. If the scopes granted are identical to the scoped requested, then this parameter is optional.

  
{
  "access_token": "ya29.a0AfH6SMDk2_lDz...1m3kE-FOF0...",
  "token_type": "bearer",
  "expires_in": 3600
}
  

Here’s a sample implementation in Python using the requests library:

  
import requests

token_url = "https://authorization-server.com/oauth/token"
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
scope = "read write"

# Send request for access token
response = requests.post(
    token_url,
    data={
        'grant_type': 'client_credentials',
        'client_id': client_id,
        'client_secret': client_secret,
        'scope': scope
    }
)

# Get the access token from the response
access_token = response.json().get('access_token')
print("Access Token:", access_token)
  

If there is something wrong with the request the server will return an error (usually with an HTTP 400 or 401 status code). Optionally, the response will include an error_description and an error_uri (used to link to docs). Common errors you may encounter are:

  • invalid_request: There is something wrong with the input parameters (missing one, includes an unsupported one, etc).
  • invalid_client: Invalid Client ID or Secret.
  • invalid_scope: There is an invalid scope in the request.

Once the client receives and validates the access token, it can use this token to make authorized API requests to the resource server. The access token is included in the HTTP request header as a bearer token.

  
GET /protected-resource HTTP/1.1
Host: api-server.com
Authorization: Bearer ya29.a0AfH6SMDk2_lDz...1m3kE-FOF0...
  

This is what this would look like in Python:

  
api_url = "https://api-server.com/protected-resource"
headers = {
    'Authorization': f'Bearer {access_token}'
}

response = requests.get(api_url, headers=headers)
print(response.json())
  

The Resource Server (i.e., the server holding the resources that the app wants to access) validates the token and returns the requested info. That’s it!

Conclusion

The Client Credentials flow in OAuth 2.0 is an essential authorization flow for applications and services that need to authenticate and interact with APIs without involving user interaction. It provides secure, efficient, and automated machine-to-machine communication, making it ideal for server-to-server integrations, microservices, and internal API access.

By following the steps outlined above, you can implement the Client Credentials flow in your application and ensure that your clients can access protected resources in a secure and standardized way. Whether you're building a cloud service, an internal tool, or an API, the Client Credentials flow offers a powerful solution for managing authentication and access in a wide range of use cases.

In this article

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.