Skip to main content

OAuth2 client credentials flow

OAuth2 is a protocol that allows third-party applications to access a user's data, without having to expose their credentials to the third-party application. OAuth2 provides a number of different flows to accomplish this goal, and one of the most commonly used is the Client Credentials flow.

The Client Credentials flow is used when an application needs to access its own resources, rather than a user's resources. In this flow, the application sends its client ID and client secret to the authorization server, and in return receives an access token that can be used to access protected resources.

info

The JavaScript example code contained in this article is exemplary and explains what happens under the hood. Everyone should use tried and tested open source libraries to consume OAuth2 and OpenID Connect. Writing this code by oneself should not be done, as you would not write your own SHA512 library.

Basic authentication

The first and most common mechanism for authenticating a client in the Client Credentials flow is Basic Authentication. In this mechanism, the client sends its client ID and client secret as part of the Authorization header in an HTTP request. The The Authorization header contains a Base64-encoded string of {URL-encoded-client-ID}:{URL-encoded-client-secret}.

To create a client capable of using the Basic Authentication mechanism with the Ory CLI, run the following command:

ory create oauth2-client --project "$PROJECT_ID" \
--name "Client Credentials Demo" \
--grant-type client_credentials \
--token-endpoint-auth-method client_secret_basic

To use the created client to obtain an access token using Basic Authentication, refer to the following code example:

const clientID = "the-client-id"
const clientSecret = "the-secret-id"
const basicAuth = bas64_encode(url_encode(clientID) + ":" + url_encode(clientSecret))

const requestOptions = {
method: "POST",
headers: {
Authorization: "Basic " + basicAuth,
"Content-Type": "application/x-www-form-urlencoded",
},
body: "grant_type=client_credentials&scope=read",
}

fetch("https://your-project.projects.oryapis.com/oauth2/token", requestOptions)
.then((response) => response.json())
.then((data) => console.log(data))

Body authentication

In the Body Authentication mechanism, the client sends its client ID and client secret as parameters in the body of the HTTP request. This is similar to Basic Authentication, but instead of sending the credentials in the Authorization header, they are sent in the request body.

To create a client capable of using the Body Authentication mechanism with the Ory CLI, run the following command:

ory create oauth2-client --project "$PROJECT_ID" \
--name "Client Credentials Demo" \
--grant-type client_credentials \
--token-endpoint-auth-method client_secret_post

To use the created client to obtain an access token using Body Authentication, refer to the following code example:

const clientID = "the-client-id"
const clientSecret = "the-secret-id"

const qs = new URLSearchParams()
qs.set("grant_type", "client_credentials")
qs.set("client_id", clientID)
qs.set("client_secret", clientSecret)
qs.set("scope", read)

const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: qs.toString(),
}

fetch("https://your-project.projects.oryapis.com/oauth2/token", requestOptions)
.then((response) => response.json())
.then((data) => console.log(data))

JWT Bearer profile for OAuth2 client authentication

The JWT Bearer Grant Type mechanism is similar to Basic and Body Authentication, but instead of sending the client ID and client secret, the client sends a JSON Web Token (JWT) which was signed by its cryptographic key.

To create a client capable of using the JWT Bearer Grant Type mechanism with Ory, you can use the following CLI command:

ory create oauth2-client --project "$PROJECT_ID" \
--name "Client Credentials Demo" \
--grant-type client_credentials \
--token-endpoint-auth-method private_key_jwt \
--jwks-uri https://example.org/path/to/clients/public_key_set.jwks
note

Using the HTTP API, you can also send the JSON Web Key Set as part of the payload (key jwks) when creating the client. This is not possible using the Ory CLI.

When authenticating the client at the token endpoint, you generate and sign the JSON Web Token with the following claims:

  • iss: REQUIRED. Issuer. This MUST contain the client_id of the OAuth Client.
  • sub: REQUIRED. Subject. This MUST contain the client_id of the OAuth Client.
  • aud: REQUIRED. Audience. The aud (audience) claim. Value that identifies the Authorization Server (Ory Hydra) as an intended audience. The Authorization Server MUST verify that it's an intended audience for the token. The Audience SHOULD be the URL of the Authorization Server's Token Endpoint.
  • jti: REQUIRED. JWT ID. A unique identifier for the token, which can be used to prevent reuse of the token. These tokens MUST only be used once, unless conditions for reuse were negotiated between the parties; any such negotiation is beyond the scope of this specification.
  • exp: REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing.
  • iat: OPTIONAL. Time at which the JWT was issued.

To use the created client to obtain an access token using the JWT Bearer profile, refer to the following code example:

const clientID = "the-client-id"

const qs = new URLSearchParams()
qs.set("grant_type", "client_credentials")
qs.set("client_id", clientID)
qs.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
qs.set(
"client_assertion",
createSignedJWT(
{
iss: "https://example.org",
sub: clientID,
aud: "https://your-project.projects.oryapis.com",
jti: randomUUID(),
exp: Math.floor((Date.now() + 1000 * 60 * 60) / 1000),
iat: Date.now(),
},
privateKey,
),
)

const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: qs.toString(),
}

fetch("https://your-project.projects.oryapis.com/oauth2/token", requestOptions)
.then((response) => response.json())
.then((data) => console.log(data))