# Client Credentials (OIDC) – M2M JWT access token (60 min TTL)

Documentation owner: unversioned-area `use-cases`; documentation version: unversioned.
Source: `content/use-cases/fortified-id-access/auth.-methods-oidc/client-credentials-oidc-m2m-jwt-access-token-60-min-ttl.md` at commit `eaf7a020437971baeee95e87eaf963636040ac71`.

### Summary of use case

This use case shows how to configure **FortifiedID Access (OIDC OP)** to support the **`client_credentials` grant** for **Machine-to-Machine (M2M)**, where the token endpoint returns an **access token as a JWT** created by a **pipe** with **TTL = 60 minutes**.\
(Ref: OIDC Authorization Code Flow / client\_credentials section – docs.fortifiedid.se)

***

### Scenario

A backend service (OIDC Relying Party / client) authenticates with `client_id` and `client_secret` against the **OIDC token endpoint** using `grant_type=client_credentials`.

FortifiedID Access executes a dedicated **client credentials pipe**, which:

1. Creates a signed **JWT access token**.
2. Sets the token lifetime to **60 minutes**.
3. Returns a standard OAuth 2.0 token response:
   * `access_token`
   * `token_type`
   * `expires_in`
   * `scope`

This flow is typically used for **machine-to-machine (M2M)** integrations where no end-user is involved.

***

### Prerequisite

There are some prerequisites for this use case. You will need the following:

* A running **FortifiedID Access** installation.
* A completed **OIDC basic installation** (tenant/provider configured and reachable).
* An OIDC **Relying Party (RP)** configured with:
  * `client_id`
  * `client_secret`
* A configured **keystore** containing a signing certificate for JWT creation.
* **Good knowledge of FortifiedID Access**, including:
  * Understanding of pipes and valves
  * Basic OIDC configuration structure
  * How to edit and deploy configuration files
  * How the OIDC module and RP configuration are structured

> Note: This guide assumes you are familiar with FortifiedID Access configuration concepts and comfortable modifying OIDC module settings.

***

### Configuration

#### 1. Enable `client_credentials` in discovery metadata

In the **OIDC module** configuration, ensure `client_credentials` is included in `grant_types_supported` inside `discovery_meta`.

```json
"grant_types_supported": [
  "authorization_code",
  "refresh_token",
  "client_credentials"
]
```

***

#### 2. Connect a pipe to the RP for client\_credentials

In your **Relying Party (RP)** configuration, reference the pipe that should handle the client credentials flow:

```json
"client_credentials_pipe_id": "client_credentials_pipe"
```

#### Example: Minimal RP configuration (client\_credentials)

Below is a simple example of how an RP (machine-to-machine client) can be configured for the `client_credentials` grant. The RP references the pipe used to create the JWT access token.

```
{
  "client_id": "m2m",
  "client_secret": "${secrets.1ILhU8IiKwG4FnvUTnEbQ8nprseHt9wxbgaRw2+AqZ1ukvw7oYQfXK1Xqt+AeRJaMY7dVezHeKcxbyYw}",
  "client_credentials_pipe_id": "client_credentials_pipe"
}
```

> **Important:**\
> Replace the example values with your own environment settings:
>
> * Set `client_id` to your preferred client identifier.
> * Set `client_secret` to your own secret reference/value (according to how secrets are managed in your setup).
> * If you use a different pipe id, update `client_credentials_pipe_id` accordingly.

***

#### 3. Add the client credentials pipe (JWT access token, TTL 60 minutes)

Add the following pipe configuration to your pipes section.

```json
{
  "id": "client_credentials_pipe",
  "config": {
    "valves": [
      {
        "name": "CreateJwt",
        "enabled": true,
        "config": {
          "dest": "access_token",
          "jwt_ttl": 60,
          "jwt_headers": {
            "typ": "at+jwt"
          },
          "jwt_claims": {
            "iss": "${globals.default_login_location}/oidc/m2m",
            "sub": "${request.client_id}",
            "aud": "https://api.example.com/",
            "client_id" : "${request.client_id}",
            "test_claim": "test_claim"
          },
          "keystore": {
            "path": "${system.customer_home}/resources/keystore.p12",
            "password": "supersecret",
            "type": "PKCS12"
          },
          "keystore_password": "supersecret",
          "keystore_alias": "oidc_sign"
        }
      },
      {
        "name": "JsonObjectCreate",
        "enabled": true,
        "config": {
          "src": {
            "access_token": "${item.access_token}",
            "token_type": "Bearer",
            "expires_in": 3600,
            "scope": "read"
          },
          "dest": "client_credentials"
        }
      }
    ]
  }
}
```

This pipe:

* Creates a signed JWT access token
* Sets the TTL to 60 minutes (`jwt_ttl: 60`)
* Returns a standard OAuth 2.0 token response
* Uses the configured keystore for signing

> **Important:**\
> The JWT headers and claims in the example above are placeholders.\
> You must adapt the `jwt_claims` (and, if required, `jwt_headers`) to match your specific environment, security requirements, and integration needs.
>
> In addition, both `jwt_ttl` and the `keystore` configuration must be adjusted according to your security policies and operational requirements.
>
> * `jwt_ttl` should reflect your organization’s token lifetime policy.
> * The `keystore` path, type, alias, and passwords must match your actual signing certificate configuration.
>
> For example, you may need to include:
>
> * `iss` (issuer)
> * `aud` (audience)
> * `sub` aligned with your client or service identity
> * Custom claims required by the consuming API
>
> Always ensure that the claims structure and signing configuration comply with your target resource server’s validation logic and your organization’s security policies.

### Test the use case

#### 1. Verify OIDC well-known configuration

Open:

```
https://<HOST>:<PORT>/oidc/<TENANT>/.well-known/openid-configuration
```

Verify:

* The endpoint is reachable
* `grant_types_supported` contains `client_credentials`
* The `token_endpoint` is correctly published

***

#### 2. Call the token endpoint

Use Postman or curl.

**Token request:**

* Method: `POST`
* URL: `<token_endpoint>`
* Authentication: `client_secret_basic` (or according to your RP configuration)
* Body (x-www-form-urlencoded):

```
grant_type=client_credentials
```

***

#### Expected result

A successful response should return:

```json
{
  "access_token": "<JWT>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read"
}
```

* `access_token` is a signed JWT
* `expires_in` is `3600` seconds (60 minutes)
* The token is created by the configured pipe
