Skip to content

Commit 20248ed

Browse files
bisbebclaude
andcommitted
docs: add client_credentials grant enhancement doc and config example
Adds a Dex Enhancement Proposal for the client_credentials grant (RFC 6749 Section 4.4) implemented in #4583, and documents the clientCredentials grantTypes entry in config.yaml.dist. Closes #3660 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ab64ed7 commit 20248ed

2 files changed

Lines changed: 210 additions & 0 deletions

File tree

config.yaml.dist

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,21 @@ web:
138138
# enforce: false
139139
# # Supported code challenge methods. Defaults to ["S256", "plain"].
140140
# codeChallengeMethodsSupported: ["S256", "plain"]
141+
#
142+
# # Explicitly set the list of grant types the server advertises and accepts.
143+
# # When omitted, the default list is used (authorization_code, implicit,
144+
# # password, refresh_token, device_code, token-exchange). Add
145+
# # "client_credentials" to enable the OAuth2 client_credentials grant
146+
# # (RFC 6749 Section 4.4) for machine-to-machine (M2M) authentication.
147+
# # Confidential clients only; public clients are always rejected.
148+
# # grantTypes:
149+
# # - authorization_code
150+
# # - implicit
151+
# # - password
152+
# # - refresh_token
153+
# # - urn:ietf:params:oauth:grant-type:device_code
154+
# # - urn:ietf:params:oauth:grant-type:token-exchange
155+
# # - client_credentials
141156

142157
# Static clients registered in Dex by default.
143158
#
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Dex Enhancement Proposal (DEP) 3660 - 2026-03-03 - Client Credentials Grant
2+
3+
## Table of Contents
4+
5+
- [Summary](#summary)
6+
- [Motivation](#motivation)
7+
- [Goals](#goals)
8+
- [Non-Goals](#non-goals)
9+
- [Proposal](#proposal)
10+
- [User Experience](#user-experience)
11+
- [Implementation Details](#implementation-details)
12+
- [Risks and Mitigations](#risks-and-mitigations)
13+
- [Future Improvements](#future-improvements)
14+
15+
## Summary
16+
17+
[RFC 6749 Section 4.4] defines the `client_credentials` grant type for service-to-service
18+
authentication where no end-user is involved. This DEP proposes implementing this grant in Dex,
19+
gated behind an opt-in configuration flag, so that machine clients can obtain tokens directly
20+
without requiring a browser-based redirect flow.
21+
22+
[RFC 6749 Section 4.4]: https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
23+
24+
## Context
25+
26+
This has been a long-standing community request:
27+
28+
- [#3660 Support client_credentials grant type] is the canonical issue tracking this request
29+
- [#926 Support resource owner password credentials grant] highlights the broader need for
30+
non-interactive flows in automated environments
31+
- [#2657 Get OIDC token issued by Dex using a token issued by one of the connectors] solved
32+
a related problem via token exchange (RFC 8693), but does not cover the pure M2M case
33+
where no upstream user identity exists
34+
35+
Common use cases reported by the community:
36+
37+
- CI/CD pipelines authenticating against ArgoCD or other Dex-protected APIs
38+
- Kubernetes operators and controllers making authenticated API calls
39+
- Service meshes and microservices authenticating without a human in the loop
40+
41+
[#3660 Support client_credentials grant type]: https://github.com/dexidp/dex/issues/3660
42+
[#926 Support resource owner password credentials grant]: https://github.com/dexidp/dex/issues/926
43+
[#2657 Get OIDC token issued by Dex using a token issued by one of the connectors]: https://github.com/dexidp/dex/issues/2657
44+
45+
## Motivation
46+
47+
### Goals
48+
49+
- Allow confidential machine clients to authenticate directly against Dex's `/token` endpoint
50+
using their client ID and secret, without any user interaction
51+
- Ensure no behavior change for existing deployments by defaulting the feature to disabled
52+
- Support standard scopes (`openid`, `email`, `profile`, `groups`) so that downstream
53+
applications can use the resulting token with their existing authorization logic
54+
55+
### Non-Goals
56+
57+
- Support for public clients: the `client_credentials` grant requires a confidential client
58+
with a non-empty secret
59+
- Refresh token issuance: M2M clients are expected to re-authenticate rather than hold
60+
long-lived refresh tokens
61+
- Custom claim sources: claims are derived from the static client configuration, not from
62+
a connector backend
63+
64+
## Proposal
65+
66+
### User Experience
67+
68+
Enable the grant by adding `client_credentials` to the `oauth2.grantTypes` list. When
69+
`grantTypes` is omitted, Dex uses a default list that does not include `client_credentials`,
70+
so the entry must be explicit:
71+
72+
```yaml
73+
oauth2:
74+
grantTypes:
75+
- authorization_code
76+
- implicit
77+
- password
78+
- refresh_token
79+
- urn:ietf:params:oauth:grant-type:device_code
80+
- urn:ietf:params:oauth:grant-type:token-exchange
81+
- client_credentials
82+
```
83+
84+
Register a confidential static client (non-empty `secret`, no `public: true`):
85+
86+
```yaml
87+
staticClients:
88+
- id: my-service
89+
secret: my-service-secret
90+
name: My Service
91+
```
92+
93+
Request a token via HTTP Basic authentication:
94+
95+
```bash
96+
curl -X POST https://dex.example.com/token \
97+
-u "my-service:my-service-secret" \
98+
-d "grant_type=client_credentials"
99+
```
100+
101+
To receive an ID token in addition to the access token, include the `openid` scope:
102+
103+
```bash
104+
curl -X POST https://dex.example.com/token \
105+
-u "my-service:my-service-secret" \
106+
-d "grant_type=client_credentials&scope=openid+profile"
107+
```
108+
109+
The response follows the standard OAuth2 token response format:
110+
111+
```json
112+
{
113+
"access_token": "...",
114+
"token_type": "bearer",
115+
"expires_in": 86400
116+
}
117+
```
118+
119+
With `scope=openid`, an `id_token` is included in the response as well.
120+
121+
**Token claims** are derived from the client itself:
122+
123+
| Claim | Value |
124+
|---|---|
125+
| `sub` | base64-encoded protobuf of (client ID, empty connector ID) |
126+
| `aud` | client ID |
127+
| `name` / `preferred_username` | client name (requires `profile` scope) |
128+
| `groups` | groups from `clientCredentialsClaims.groups` on the client (requires `groups` scope) |
129+
130+
**Rejected scopes:** `offline_access` and `federated:id` are not supported; requesting them
131+
returns an error.
132+
133+
#### ArgoCD example
134+
135+
When using Dex as the OIDC provider for ArgoCD, CI/CD pipelines can authenticate
136+
programmatically without a user session:
137+
138+
```yaml
139+
# dex config
140+
oauth2:
141+
grantTypes:
142+
- authorization_code
143+
- implicit
144+
- password
145+
- refresh_token
146+
- urn:ietf:params:oauth:grant-type:device_code
147+
- urn:ietf:params:oauth:grant-type:token-exchange
148+
- client_credentials
149+
150+
staticClients:
151+
- id: argocd-pipeline
152+
secret: pipeline-secret
153+
name: ArgoCD Pipeline Client
154+
```
155+
156+
```bash
157+
# Obtain a token from Dex
158+
TOKEN=$(curl -s -X POST https://dex.example.com/token \
159+
-u "argocd-pipeline:pipeline-secret" \
160+
-d "grant_type=client_credentials&scope=openid" \
161+
| jq -r .id_token)
162+
163+
# Use the token with the ArgoCD API
164+
argocd app list --auth-token "$TOKEN" --server argocd.example.com
165+
```
166+
167+
### Implementation Details
168+
169+
The grant is gated behind `clientCredentialsEnabled` in the `oauth2` config block,
170+
following the same pattern as `passwordConnector` for the `password` grant.
171+
When the flag is `false` (the default), the grant type is filtered out in `newServer()`
172+
and never advertised in the discovery document.
173+
174+
The token endpoint handler authenticates the client via HTTP Basic auth, verifies the
175+
client is confidential, and issues a signed token. No connector lookup or user session
176+
is involved.
177+
178+
Implemented in [#4583].
179+
180+
[#4583]: https://github.com/dexidp/dex/pull/4583
181+
182+
### Risks and Mitigations
183+
184+
- **Credential exposure:** client secrets used for `client_credentials` must be treated with
185+
the same care as any long-lived credential. Rotate them regularly and store them in a
186+
secrets manager (Kubernetes Secret, Vault, etc.).
187+
- **Over-privileged clients:** because claims come from the client configuration rather than
188+
a user identity, downstream applications should validate the `sub` claim carefully. Grant
189+
only the minimum required permissions to each client.
190+
- **Opt-in only:** the grant is never active unless `client_credentials` is listed in
191+
`oauth2.grantTypes` explicitly, so existing deployments are unaffected.
192+
193+
## Future Improvements
194+
195+
- Allow per-client scope restrictions to limit what a machine client can request

0 commit comments

Comments
 (0)