Service-to-service (Client Credentials + Introspection)¶
For AI Agents¶
Prompt: "Set up a service-to-service authentication example using client credentials grant and token introspection between two services"
What this example demonstrates: 1. Service A obtains access token using client credentials grant 2. Service A calls Service B with the token in Authorization header 3. Service B validates the token using introspection endpoint 4. Service B enforces scope-based authorization
Common tasks:
| Task | Prompt Example |
|---|---|
| Run example locally | "Run the service-to-service example with the Node.js resource server" |
| Test client credentials | "Get an access token for service A using client credentials" |
| Validate token | "Call the introspection endpoint to validate a token and check scopes" |
| Deploy to K8s | "Deploy the service-to-service example to KIND with both services" |
| Debug auth failure | "Service B is rejecting tokens - help debug the introspection flow" |
| Add scope check | "Add a new scope requirement 'admin:write' to Service B" |
Key endpoints:
- Token request: POST /oauth/token (grant_type=client_credentials)
- Introspection: POST /oauth/introspect
- Example resource server: examples/resource-server-node/
This cookbook demonstrates a common machine-to-machine pattern:
- Service A (client) obtains an access token using the Client Credentials grant.
- Service B (resource server) validates incoming requests by calling RFC
7662 token introspection (
POST /oauth/introspect). - Service B enforces a required scope (e.g.
read).
In this repository, Service B is a tiny example app located at
examples/resource-server-node/ and is deployed in KIND via
k8s/components/resource-server/.
Architecture¶
flowchart LR
A[Service A\n(client)] -->|POST /oauth/token\nclient_credentials| AS[OAuth2 Server]
A -->|GET /protected\nAuthorization: Bearer ...| B[Service B\n(resource server)]
B -->|POST /oauth/introspect| AS
Why introspection?
- It’s straightforward to implement.
- It works even when the access token is opaque (or when you want revocation to take effect immediately).
- It centralizes validation rules in the authorization server.
Run it on KIND (automated)¶
The extended KIND E2E script provisions:
- Postgres + migrations
oauth2-serverresource-server(example)
It then:
- registers a test OAuth2 client
- mints an access token (
grant_type=client_credentials) - verifies that
resource-server: - returns 401 without a token
- returns 200 with a valid token
- returns 401 again after revocation
Run:
If you want to keep the cluster around for debugging:
How the resource server validates tokens¶
At a high level, Service B does:
- Parse the
Authorization: Bearer <token>header - Call:
POST /oauth/introspect
Content-Type: application/x-www-form-urlencoded
token=<token>&client_id=<id>&client_secret=<secret>
- Require
active=true - Require a scope (defaults to
read)
See examples/resource-server-node/server.js for the full implementation.
Notes and production guidance¶
- Cache introspection responses for a short TTL to reduce load on the auth server (but consider revocation requirements).
- Use sensible timeouts and retries when calling
/oauth/introspect. - If you need maximum performance, consider local JWT validation (but ensure your key distribution / rotation story is solid).