DEV Community

Cover image for Dynamic Client Registration in OAuth2: Why it matters, how to implement it
Saif
Saif

Posted on

Dynamic Client Registration in OAuth2: Why it matters, how to implement it

Ever felt stuck clicking through your IdP’s dashboard just to get a new client ID for your latest microservice or AI agent?

We’ve been increasingly experiencing this. When your platform onboards dozens (If not hundreds) of AI agents, manual onboarding sucks and isn’t scalable.

Enter Dynamic Client Registration (DCR). With a single API call, your apps and agents can register themselves at runtime. No human in the loop, no dashboard clicks, and frictionless. Let’s see how it works.

What is Dynamic Client Registration?

It’s an extension of the OAuth2 specification that lets clients (Like AI agents, for example) register themselves programmatically with an authorization server at runtime.

Instead of a human logging into a dashboard and manually copying credentials, an AI agent can send a registration request to the client registration endpoint and receive everything it needs to authenticate and request access tokens.

This saves weeks of effort and manual work because you don’t have to manually onboard these clients.

With static registration, each of those agents would require a manual setup:

  1. Ops creates a new client in the IdP dashboard
  2. Developer configures redirect URIs and grant types
  3. Credentials (client_id/client_secret) are hard-coded into the agent

With DCR, this is just two logical steps:

  1. Client sends metadata to the registration endpoint.
  2. ‍Authorization server responds with credentials, which the client then uses.

That’s it. Now let’s look at how to implement this with an example.

Step 1: Discovery

The agent fetches the OpenID Connect discovery document to find the DCR endpoint.

 GET /.well-known/openid-configuration
Enter fullscreen mode Exit fullscreen mode

This fetches a response like so:

{
  "issuer": "https://auth.example.com",
  "token_endpoint": "https://auth.example.com/oauth/token",
  "registration_endpoint": "https://auth.example.com/oauth/register",
  ...
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Register client

Now that you know the URL, it’s time to programmatically create your client. Give it a name, tell it which grant types it’ll use, and specify any callback URLs. In most agentic or backend-only cases, you’ll use the client credentials flow.

curl -X POST https://auth.example.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "agent-4729",
    "redirect_uris": ["https://agent-4729.example.com/callback"],
    "grant_types": ["client_credentials"],
    "token_endpoint_auth_method": "client_secret_basic"
  }'
Enter fullscreen mode Exit fullscreen mode

Step 3: Handle the registration response

Immediately after registration, the server hands you back the client_id and client_secret, plus a special registration_access_token you’ll need if you ever want to update or delete this client.

{
  "client_id": "abc123",
  "client_secret": "xyz789",
  "registration_access_token": "reg_token",
  "client_id_issued_at": 1717924800
}
Enter fullscreen mode Exit fullscreen mode
  • Store client_id + client_secret securely
  • Keep registration_access_token for future client updates or deletion

Step 4: Obtain access token

With your credentials in hand, it’s just like any other OAuth2 client: hit the token endpoint, present your secret, and ask for the scopes you need.

curl -X POST https://auth.example.com/oauth/token \
  -u "abc123:xyz789" \
  -d "grant_type=client_credentials&scope=data:read tasks:execute"
Enter fullscreen mode Exit fullscreen mode

Example response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "data:read tasks:execute"
}
Enter fullscreen mode Exit fullscreen mode

If you prefer code to shell, here’s the same flow with Axios. Great for CI/CD scripts or automated onboarding jobs:

const axios = require('axios');

// 1. Discover DCR endpoint
const { data: discovery } = await axios.get(
  'https://auth.example.com/.well-known/openid-configuration'
);
const regEndpoint = discovery.registration_endpoint;

// 2. Register the client
const { data: reg } = await axios.post(regEndpoint, {
  client_name: 'my-marketplace-app',
  redirect_uris: ['https://app.dev/callback'],
  grant_types: ['authorization_code'],
  token_endpoint_auth_method: 'client_secret_basic'
});

// 3. Request an access token
const tokenRes = await axios.post(
  'https://auth.example.com/oauth/token',
  null,
  {
    auth: { username: reg.client_id, password: reg.client_secret },
    params: {
      grant_type: 'authorization_code',
      code: 'AUTH_CODE',
      redirect_uri: 'https://app.dev/callback'
    }
  }
);

console.log('Access Token:', tokenRes.data.access_token);
Enter fullscreen mode Exit fullscreen mode

Lock it down and automate cleanup

You don’t want anyone to flood your DCR endpoint with junk clients. Protect it with:

  • mTLS: Only workloads with valid certs can register (Side note: Check out the difference between OAuth and mTLS
  • Software statements: Clients prove their legitimacy via signed JWTs
  • Bootstrap tokens: A one-time token that seeds initial registrations

And don’t forget teardown: use the registration_access_token to DELETE unused clients automatically when an integration is sunset.

Want to register clients dynamically? Take a look at this deep-dive

Your turn

Have you tried DCR in your own platform or ecosystem?

  • How did you protect your registration endpoint?
  • What cleanup or rotation strategy did you build?

Share your wins and stories below 👇

Top comments (0)