Generating a privileged token for the Agent while it acts on behalf of a user

Some agent actions are not simple user-delegated actions. They require elevated access, strict policy, and strong auditability. A privileged token lets IBM Verify represent an approved agent operation as a narrow, short-lived, policy-controlled grant instead of giving the agent broad standing access.

This tutorial demonstrates a subject and actor OAuth 2.0 Token exchange flow in IBM Verify:

  • the subject is the signed-in user;
  • the actor is the agent performing the operation;
  • the Privileged Token Exchange Application is an OAuth/OIDC application configured to use the token-exchange grant;
  • authorization_details binds the request to the exact privileged action;
  • audience identifies the protected resource for which the exchanged token is requested.
Important: This guide does not use a standalone STS client. IBM Verify supports token exchange both through standalone STS clients and as a grant type on OpenID Connect applications. This tutorial uses an OAuth/OIDC Privileged Token Exchange Application so the runtime request is authenticated with privileged_client_id / privileged_client_secret and carries the required audience value.

IBM Verify references:

Key learnings

  1. Understand subject and actor identity in a privileged agent operation.
  2. Configure separate applications for user authentication, agent authentication, and privileged token exchange.
  3. Use authorization details to constrain the requested privileged action.
  4. Exchange a user subject token and an agent actor token for a privileged token.
  5. Request the token for a specific target audience.
  6. Introspect and validate the issued privileged token.

Audience

  • IAM administrators
  • Security architects
  • Application developers integrating agent flows with IBM Verify

Architecture

Privileged-token configuration overview:

Privileged-token configuration overview diagram

The key components are:

  1. Subject Application — Authorization Code flow for the signed-in user.
  2. Actor application or Agent client — Client Credentials flow for the agent runtime; this client is associated with the agent identity during onboarding.
  3. Privileged Token Exchange Application — OAuth/OIDC application configured for the token-exchange grant. Its credentials are privileged_client_id and privileged_client_secret.
  4. Authorization Details Type (ADT) — describes the exact privileged action being requested.
  5. Protected Resource or Privileged API — the target API identified by audience; its client credentials are used for token introspection in this tutorial.
  6. IBM Verify policy — validates subject, actor, requested operation, audience, and other configured conditions before issuing the exchanged token.
Note: The configuration diagram numbers the three OAuth/OIDC application roles, while the runtime sequence numbers swimlanes and includes the End User as lane 1. Therefore, the Privileged Token Exchange Application is component 3 in the configuration view and lane 4 in the runtime view. The labels identify the same component.

Prerequisites and resources

You need:

  1. An IBM Verify tenant with Agent Identity capability enabled.
  2. Permission to create or configure OAuth/OIDC applications, authorization-details types, access policies, and the protected resource used in this tutorial.
  3. Either an existing onboarded agent identity or permission to onboard an agent and associate its runtime client.
  4. Postman or Insomnia.

This guide uses the IBM Verify Admin console to create the three OAuth/OIDC application roles used by the runtime flow. An Admin API client is also configured so API-driven agent onboarding or administration can obtain an administrative access token. The Admin API Client is not the Privileged Token Exchange Application and is never used to authenticate the runtime token-exchange call.

Resources provided:

  1. Guide 8 Insomnia API collection
    type: guide8_insomnia
    name: Guide 8 - Privileged Token for Agent Operations
    description: Guide 8 configuration helper for an Admin API token plus subject + actor OAuth 2.0 Token Exchange using an OAuth/OIDC
      Privileged Token Exchange Application. No standalone STS client is used.
    variables:
      tenant_url: ''
      admin_client_id: ''
      admin_client_secret: ''
      admin_token: ''
      subject_client_id: ''
      subject_client_secret: ''
      authorization_code: ''
      redirect_uri: http://localhost:4000/callback
      subject_token: ''
      actor_client_id: ''
      actor_client_secret: ''
      actor_token: ''
      privileged_client_id: ''
      privileged_client_secret: ''
      audience: ''
      privileged_authorization_details: '[{"type":"urn:ibm:params:oauth:request:agent_privileged_action","actions":["reset-password"],"resource":"verify-user-admin"}]'
      resource_client_id: ''
      resource_client_secret: ''
      privileged_token: ''
    folders:
    - name: Configuration
    - name: Runtime Flow
    requests:
    - name: Get Admin Token
      folder: Configuration
      method: POST
      url: '{{ tenant_url }}/oauth2/token'
      body:
        mimeType: application/x-www-form-urlencoded
        params:
        - name: grant_type
          value: client_credentials
        - name: client_id
          value: '{{ admin_client_id }}'
        - name: client_secret
          value: '{{ admin_client_secret }}'
      description: Obtain an administrative access token for agent onboarding/administration APIs. Copy access_token to admin_token.
        This token is not used by Runtime Flow.
    - name: Get Subject Token
      folder: Runtime Flow
      method: POST
      url: '{{ tenant_url }}/oauth2/token'
      body:
        mimeType: application/x-www-form-urlencoded
        params:
        - name: grant_type
          value: authorization_code
        - name: client_id
          value: '{{ subject_client_id }}'
        - name: client_secret
          value: '{{ subject_client_secret }}'
        - name: code
          value: '{{ authorization_code }}'
        - name: redirect_uri
          value: '{{ redirect_uri }}'
    - name: Get Actor Token
      folder: Runtime Flow
      method: POST
      url: '{{ tenant_url }}/oauth2/token'
      body:
        mimeType: application/x-www-form-urlencoded
        params:
        - name: grant_type
          value: client_credentials
        - name: client_id
          value: '{{ actor_client_id }}'
        - name: client_secret
          value: '{{ actor_client_secret }}'
    - name: Exchange Subject and Actor Tokens for Privileged Token
      folder: Runtime Flow
      method: POST
      authentication:
        type: basic
        username: '{{ privileged_client_id }}'
        password: '{{ privileged_client_secret }}'
      url: '{{ tenant_url }}/oauth2/token'
      body:
        mimeType: application/x-www-form-urlencoded
        params:
        - name: grant_type
          value: urn:ietf:params:oauth:grant-type:token-exchange
        - name: subject_token
          value: '{{ subject_token }}'
        - name: subject_token_type
          value: urn:ietf:params:oauth:token-type:access_token
        - name: actor_token
          value: '{{ actor_token }}'
        - name: actor_token_type
          value: urn:ietf:params:oauth:token-type:access_token
        - name: requested_token_type
          value: urn:ietf:params:oauth:token-type:access_token
        - name: audience
          value: '{{ audience }}'
        - name: authorization_details
          value: '{{ privileged_authorization_details }}'
    - name: Introspect Privileged Token
      folder: Runtime Flow
      method: POST
      authentication:
        type: basic
        username: '{{ resource_client_id }}'
        password: '{{ resource_client_secret }}'
      url: '{{ tenant_url }}/oauth2/introspect'
      body:
        mimeType: application/x-www-form-urlencoded
        params:
        - name: token
          value: '{{ privileged_token }}'
  2. Guide 8 Postman API collection
    {
      "info": {
        "_postman_id": "1b3cf1c8-a9f2-4f56-bef0-guide8privileged",
        "name": "Guide 8 - Privileged Token for Agent Operations",
        "description": "Runnable Guide 8 collection with a Configuration helper for the Admin API token plus the subject + actor OAuth 2.0 Token Exchange runtime flow. Runtime exchange uses the OAuth/OIDC Privileged Token Exchange Application; no standalone STS client is used.",
        "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
      },
      "item": [
        {
          "name": "Configuration",
          "description": "Administrative setup helpers. Get Admin Token is for agent onboarding/administration only; it is not part of Runtime Flow.",
          "item": [
            {
              "name": "Get Admin Token",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/x-www-form-urlencoded"
                  }
                ],
                "body": {
                  "mode": "urlencoded",
                  "urlencoded": [
                    {
                      "key": "grant_type",
                      "value": "client_credentials",
                      "type": "text"
                    },
                    {
                      "key": "client_id",
                      "value": "{{admin_client_id}}",
                      "type": "text"
                    },
                    {
                      "key": "client_secret",
                      "value": "{{admin_client_secret}}",
                      "type": "text"
                    }
                  ]
                },
                "url": {
                  "raw": "{{tenant_url}}/oauth2/token",
                  "host": [
                    "{{tenant_url}}"
                  ],
                  "path": [
                    "oauth2",
                    "token"
                  ]
                },
                "description": "Obtain an administrative access token for agent onboarding/administration APIs. This token is not used by Runtime Flow token exchange."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.response.code >= 200 && pm.response.code < 300) {",
                      "  const json = pm.response.json();",
                      "  if (json.access_token) {",
                      "    pm.collectionVariables.set(\"admin_token\", json.access_token);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "Runtime Flow",
          "item": [
            {
              "name": "Get Subject Token",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/x-www-form-urlencoded"
                  }
                ],
                "body": {
                  "mode": "urlencoded",
                  "urlencoded": [
                    {
                      "key": "grant_type",
                      "value": "authorization_code",
                      "type": "text"
                    },
                    {
                      "key": "client_id",
                      "value": "{{subject_client_id}}",
                      "type": "text"
                    },
                    {
                      "key": "client_secret",
                      "value": "{{subject_client_secret}}",
                      "type": "text"
                    },
                    {
                      "key": "code",
                      "value": "{{authorization_code}}",
                      "type": "text"
                    },
                    {
                      "key": "redirect_uri",
                      "value": "{{redirect_uri}}",
                      "type": "text"
                    }
                  ]
                },
                "url": {
                  "raw": "{{tenant_url}}/oauth2/token",
                  "host": [
                    "{{tenant_url}}"
                  ],
                  "path": [
                    "oauth2",
                    "token"
                  ]
                }
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.response.code >= 200 && pm.response.code < 300) {",
                      "  const json = pm.response.json();",
                      "  if (json.access_token) {",
                      "    pm.collectionVariables.set(\"subject_token\", json.access_token);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Get Actor Token",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/x-www-form-urlencoded"
                  }
                ],
                "body": {
                  "mode": "urlencoded",
                  "urlencoded": [
                    {
                      "key": "grant_type",
                      "value": "client_credentials",
                      "type": "text"
                    },
                    {
                      "key": "client_id",
                      "value": "{{actor_client_id}}",
                      "type": "text"
                    },
                    {
                      "key": "client_secret",
                      "value": "{{actor_client_secret}}",
                      "type": "text"
                    }
                  ]
                },
                "url": {
                  "raw": "{{tenant_url}}/oauth2/token",
                  "host": [
                    "{{tenant_url}}"
                  ],
                  "path": [
                    "oauth2",
                    "token"
                  ]
                }
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.response.code >= 200 && pm.response.code < 300) {",
                      "  const json = pm.response.json();",
                      "  if (json.access_token) {",
                      "    pm.collectionVariables.set(\"actor_token\", json.access_token);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Exchange Subject and Actor Tokens for Privileged Token",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/x-www-form-urlencoded"
                  }
                ],
                "body": {
                  "mode": "urlencoded",
                  "urlencoded": [
                    {
                      "key": "grant_type",
                      "value": "urn:ietf:params:oauth:grant-type:token-exchange",
                      "type": "text"
                    },
                    {
                      "key": "subject_token",
                      "value": "{{subject_token}}",
                      "type": "text"
                    },
                    {
                      "key": "subject_token_type",
                      "value": "urn:ietf:params:oauth:token-type:access_token",
                      "type": "text"
                    },
                    {
                      "key": "actor_token",
                      "value": "{{actor_token}}",
                      "type": "text"
                    },
                    {
                      "key": "actor_token_type",
                      "value": "urn:ietf:params:oauth:token-type:access_token",
                      "type": "text"
                    },
                    {
                      "key": "requested_token_type",
                      "value": "urn:ietf:params:oauth:token-type:access_token",
                      "type": "text"
                    },
                    {
                      "key": "audience",
                      "value": "{{audience}}",
                      "type": "text"
                    },
                    {
                      "key": "authorization_details",
                      "value": "{{privileged_authorization_details}}",
                      "type": "text"
                    }
                  ]
                },
                "url": {
                  "raw": "{{tenant_url}}/oauth2/token",
                  "host": [
                    "{{tenant_url}}"
                  ],
                  "path": [
                    "oauth2",
                    "token"
                  ]
                },
                "auth": {
                  "type": "basic",
                  "basic": [
                    {
                      "key": "username",
                      "value": "{{privileged_client_id}}",
                      "type": "string"
                    },
                    {
                      "key": "password",
                      "value": "{{privileged_client_secret}}",
                      "type": "string"
                    }
                  ]
                }
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.response.code >= 200 && pm.response.code < 300) {",
                      "  const json = pm.response.json();",
                      "  if (json.access_token) {",
                      "    pm.collectionVariables.set(\"privileged_token\", json.access_token);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "Introspect Privileged Token",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/x-www-form-urlencoded"
                  }
                ],
                "body": {
                  "mode": "urlencoded",
                  "urlencoded": [
                    {
                      "key": "token",
                      "value": "{{privileged_token}}",
                      "type": "text"
                    }
                  ]
                },
                "url": {
                  "raw": "{{tenant_url}}/oauth2/introspect",
                  "host": [
                    "{{tenant_url}}"
                  ],
                  "path": [
                    "oauth2",
                    "introspect"
                  ]
                },
                "auth": {
                  "type": "basic",
                  "basic": [
                    {
                      "key": "username",
                      "value": "{{resource_client_id}}",
                      "type": "string"
                    },
                    {
                      "key": "password",
                      "value": "{{resource_client_secret}}",
                      "type": "string"
                    }
                  ]
                }
              },
              "response": []
            }
          ]
        }
      ],
      "variable": [
        {
          "key": "tenant_url",
          "value": ""
        },
        {
          "key": "admin_client_id",
          "value": ""
        },
        {
          "key": "admin_client_secret",
          "value": ""
        },
        {
          "key": "admin_token",
          "value": ""
        },
        {
          "key": "subject_client_id",
          "value": ""
        },
        {
          "key": "subject_client_secret",
          "value": ""
        },
        {
          "key": "authorization_code",
          "value": ""
        },
        {
          "key": "redirect_uri",
          "value": "http://localhost:4000/callback"
        },
        {
          "key": "subject_token",
          "value": ""
        },
        {
          "key": "actor_client_id",
          "value": ""
        },
        {
          "key": "actor_client_secret",
          "value": ""
        },
        {
          "key": "actor_token",
          "value": ""
        },
        {
          "key": "privileged_client_id",
          "value": ""
        },
        {
          "key": "privileged_client_secret",
          "value": ""
        },
        {
          "key": "audience",
          "value": ""
        },
        {
          "key": "privileged_authorization_details",
          "value": "[{\"type\":\"urn:ibm:params:oauth:request:agent_privileged_action\",\"actions\":[\"reset-password\"],\"resource\":\"verify-user-admin\"}]"
        },
        {
          "key": "resource_client_id",
          "value": ""
        },
        {
          "key": "resource_client_secret",
          "value": ""
        },
        {
          "key": "privileged_token",
          "value": ""
        }
      ]
    }

Prepare the API collection

Import either the Postman or Insomnia collection and set variables as you complete the following configuration sections.

Variable Set after Purpose
tenant_url Tenant available Base tenant URL, for example https://tenant.example.com
admin_client_id / admin_client_secret Create Admin API Client Credentials used only to obtain an admin token for agent onboarding/administration APIs
admin_token Get Admin Token Administrative access token; intentionally not used by Runtime Flow requests
subject_client_id / subject_client_secret Create Subject Application OAuth client for the signed-in user
redirect_uri Create Subject Application Redirect URI registered for Authorization Code flow
authorization_code User authentication Short-lived code returned to the redirect URI after the user signs in
actor_client_id / actor_client_secret Create Actor Application / Agent Client Agent runtime credentials
privileged_client_id / privileged_client_secret Create Privileged Token Exchange Application Client credentials that authenticate the token-exchange request
audience Configure Protected Resource Target resource identifier requested during token exchange
resource_client_id / resource_client_secret Configure Protected Resource Credentials used to introspect the exchanged token
privileged_authorization_details Included by default JSON describing the privileged action

Runtime token variables are:

  • subject_token
  • actor_token
  • privileged_token

The Postman collection automatically saves admin_token, subject_token, actor_token, and privileged_token from successful responses. In Insomnia, copy each returned access_token into the corresponding environment variable before it is needed.

Note: admin_token belongs only to configuration/onboarding. It is not sent in the subject + actor token-exchange flow. There are deliberately no sts_client_id, sts_client_secret, privileged_sts_client_id, privileged_sts_client_secret, or resource_audience variables in this collection. agent_id is an onboarding/administrative identifier, not a runtime token-exchange form parameter.

Configuration

Step 0: Create the Admin API client

The Admin API client is the administrative credential used when agent onboarding or client registration is performed through IBM Verify APIs. It is separate from all three runtime OAuth/OIDC application roles.

  1. Open the IBM Verify Admin Console.
  2. Go to Security > API access and select Add API client.
    Grant only the entitlements required by the administrative operations you perform:
    • readAgents — read agent records.
    • writeAgents — create/update agent records.
    • manageAgentStatus — manage agent lifecycle/status when your onboarding flow requires it.
    • manageOidcDynamicClient — add this only when your onboarding automation uses Dynamic Client Registration or OIDC-client management APIs.
  3. Do not add manageSTSClients for this tutorial.
  4. Save the API client and copy its credentials into admin_client_id and admin_client_secret.
  5. Run Configuration > Get Admin Token from the supplied collection.
  6. Confirm an access token is returned. Postman saves it as admin_token; in Insomnia, copy the returned access_token into admin_token.
curl -X POST "{{tenant_url}}/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id={{admin_client_id}}" \
  -d "client_secret={{admin_client_secret}}"
Note: The Admin API client authorizes IBM Verify administrative API calls. The admin_token is not used to request the privileged runtime token.

Step 1: Create the Subject application

The Subject Application represents the signed-in user. For this tutorial, create it through the Admin Console.

  1. In the Admin Console, go to Applications > Applications and add or configure an OpenID Connect application.
  2. Enable the Authorization Code grant.
  3. Register the redirect URI used by your demo/API client, for example http://localhost:4000/callback.
  4. Save the client credentials as subject_client_id and subject_client_secret.
  5. Set redirect_uri in the collection to the exact registered redirect URI.

Obtain authorization_code

Open a browser to the authorization endpoint:

{{tenant_url}}/oauth2/authorize?client_id={{subject_client_id}}&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A4000%2Fcallback&scope=openid%20profile&state=<random-value>
  1. Open the URL in a browser.
  2. Authenticate the end user and complete any required consent or policy steps.
  3. Verify redirects the browser to your registered callback, for example http://localhost:4000/callback?code=<AUTHORIZATION_CODE>&state=<random-value>.
  4. Copy only the value of the code query parameter into the collection variable authorization_code.
  5. Run Runtime Flow > Get Subject Token before the authorization code expires; authorization codes are short-lived and single-use.
Note: If the Subject Application requires PKCE, the authorization request must also contain code_challenge and code_challenge_method=S256, and the token request must send the matching code_verifier.

Step 2: Create the Actor application or Agent client

The Actor application or Agent client is the agent runtime credential.

  1. In the Admin Console, go to Applications > Applications and create/configure the OAuth/OIDC application used by the agent runtime.
  2. Enable the Client Credentials grant.
  3. Save the generated credentials as actor_client_id and actor_client_secret.
  4. Do not obtain the actor token yet if the agent-to-client association has not been completed.

Step 3: Onboard the agent and map the Actor application or Agent client

The agent record represents the agent as a first-class identity. The runtime actor token must be resolvable to that agent through the client-to-agent association established during onboarding.

  • Existing onboarded agent: verify that the actor_client_id is already associated with the correct agent identity.
  • New agent: first run Configuration > Get Admin Token, then use the Agent Onboarding guide/API flow to create the agent record and associate the Actor application or Agent client with it.
Create Admin API Client
        ↓
Get Admin Token
        ↓
Create Actor Application / Agent Client
        ↓
Onboard or update Agent and map actor_client_id
        ↓
Only then run Runtime Flow
Note: Do not run the Runtime Flow folder as part of onboarding. Retain the resulting agent_id for administration, audit, and troubleshooting. It is intentionally not sent as a form parameter in the token-exchange request.

Step 4: Create the Authorization Details Type

Authorization details describe the privileged operation being requested and bind the request to the exact operation.

Create or configure the following tutorial ADT:

Field Value
Name Agent Privileged Action
Type urn:ibm:params:oauth:request:agent_privileged_action
Purpose Privileged agent operation performed with both subject and actor context

Use this payload in the token-exchange request:

[
  {
    "type": "urn:ibm:params:oauth:request:agent_privileged_action",
    "actions": ["reset-password"],
    "resource": "verify-user-admin"
  }
]

Step 5: Create the Privileged Token Exchange Application

Create an OAuth/OIDC application configured for OAuth 2.0 Token Exchange. Do not create or use a standalone STS client for this tutorial.

  1. In the Admin Console, go to Applications > Applications and create/configure the OpenID Connect application used specifically for privileged token exchange.
  2. Enable or configure the OAuth 2.0 Token exchange grant.
  3. Configure token-exchange trust or policy so only approved subject and actor combinations can obtain the privileged token.
  4. Bind or evaluate the ADT type urn:ibm:params:oauth:request:agent_privileged_action as required by your policy design.
  5. Configure the policy/claims needed for the target protected resource and requested audience.
  6. Keep the exchanged privileged token lifetime short.
  7. Save this application's credentials as privileged_client_id and privileged_client_secret.

Step 6: Configure the Protected resource or Privileged API

  1. Define the resource identifier that the exchanged token must target.
  2. Set that exact value in the collection variable audience.
  3. Configure the resource to validate the token's audience and required authorization details/claims.
  4. For this tutorial's introspection step, set resource_client_id and resource_client_secret.
Note: The audience variable is the single source of truth. There is no separate resource_audience variable.

Token-exchange request contract

Parameter Value
Client authentication HTTP Basic using privileged_client_id / privileged_client_secret
grant_type urn:ietf:params:oauth:grant-type:token-exchange
subject_token End-user access token from the Subject Application
subject_token_type urn:ietf:params:oauth:token-type:access_token
actor_token Agent access token from the Actor Application
actor_token_type urn:ietf:params:oauth:token-type:access_token
requested_token_type urn:ietf:params:oauth:token-type:access_token
audience Target protected-resource identifier
authorization_details Privileged ADT payload

Run the flow

Privileged-token runtime flow:

Privileged-token runtime flow diagram

Run the requests in this exact order from the collection folder Runtime Flow.

Step 1: Get Subject token

Exchange the user's authorization code for the subject access token.

curl -X POST "{{tenant_url}}/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id={{subject_client_id}}" \
  -d "client_secret={{subject_client_secret}}" \
  -d "code={{authorization_code}}" \
  -d "redirect_uri={{redirect_uri}}"

Save the returned access_token as subject_token.

Step 2: Get Actor Token

The agent obtains its runtime token using Client Credentials.

curl -X POST "{{tenant_url}}/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id={{actor_client_id}}" \
  -d "client_secret={{actor_client_secret}}"

Save the returned access_token as actor_token.

Step 3: Exchange Subject and Actor tokens for Privileged token

The Privileged Token Exchange Application authenticates to IBM Verify with privileged_client_id / privileged_client_secret and submits the subject token, actor token, requested audience, and authorization details.

curl -X POST "{{tenant_url}}/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "{{privileged_client_id}}:{{privileged_client_secret}}" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  -d "subject_token={{subject_token}}" \
  -d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
  -d "actor_token={{actor_token}}" \
  -d "actor_token_type=urn:ietf:params:oauth:token-type:access_token" \
  -d "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
  -d "audience={{audience}}" \
  --data-urlencode 'authorization_details=[{"type":"urn:ibm:params:oauth:request:agent_privileged_action","actions":["reset-password"],"resource":"verify-user-admin"}]'

Save the returned access_token as privileged_token.

Step 4: Introspect Privileged token

The protected resource validates the exchanged token.

curl -X POST "{{tenant_url}}/oauth2/introspect" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "{{resource_client_id}}:{{resource_client_secret}}" \
  -d "token={{privileged_token}}"

Validate the following:

  1. The token is active.
  2. The subject context identifies the intended user.
  3. The actor context identifies the expected agent/runtime actor.
  4. The audience matches the target privileged resource.
  5. The privileged action/authorization details match the operation being attempted.
  6. The token lifetime is short enough for the privileged operation.
Note: Do not hard-code an assumption that every tenant exposes these values under identical claim names; validate the claims and introspection mapping configured for your IBM Verify tenant.

Security recommendations

  1. Do not give the agent permanent broad administrative scopes when a narrow exchanged token can be used.
  2. Bind privileged requests to a specific action and resource using authorization details.
  3. Validate both subject and actor context before issuing the privileged token.
  4. Restrict token exchange to the intended audience and validate that audience at the resource.
  5. Keep privileged token lifetime short and avoid unnecessary refresh capability.
  6. Log the subject, agent or actor, privileged token-exchange client, requested action, audience or resource, policy decision, and token-exchange event.
  7. Use approval or human-in-the-loop controls for operations whose risk warrants an explicit approval step.

Troubleshooting

Symptom Likely cause Check
invalid_client getting admin token Wrong Admin API Client credentials admin_client_id, admin_client_secret, and whether the API client is enabled
Admin API returns 403 Admin client lacks required entitlement Add only the entitlement needed; do not add manageSTSClients for this guide
invalid_client during exchange Wrong Privileged Token Exchange Application credentials privileged_client_id, privileged_client_secret, client authentication method, and token-exchange grant configuration
invalid_grant Subject or actor token not accepted Token expiry, issuer/type, and token-exchange trust/policy
invalid_actor_token Actor token invalid or actor mapping cannot be resolved Reissue actor token and confirm actor client is associated with the onboarded agent
invalid_authorization_details ADT type or payload rejected ADT type URI, JSON structure, allowed actions/resources, and policy mapping
access_denied Policy rejected the requested privileged operation Subject criteria, actor criteria, ADT/action, audience, approval state, and policy decision logs
Audience missing or wrong audience unset or not allowed Set the single audience variable and verify the target resource/policy configuration
Introspection fails with invalid_client Resource introspection credentials are wrong/not permitted resource_client_id, resource_client_secret, and introspection authorization
Resource API rejects token Token does not satisfy resource validation aud, actor/subject context, action/authorization details, expiry, and resource validation rules

Summary

The Admin API Client is a configuration or onboarding credential only; it obtains admin_token for IBM Verify administrative APIs and does not participate in runtime token exchange.

The runtime design uses three distinct OAuth/OIDC application roles:

  • Subject application — obtains the user's subject token.
  • Actor application or Agent client — obtains the agent's actor token and is mapped to the agent identity during onboarding.
  • Privileged Token Exchange Application — authenticates the token-exchange request using privileged_client_id / privileged_client_secret and requests a token for the configured audience.

IBM Verify evaluates the subject, actor, requested privileged action, target audience, and policy before issuing a constrained privileged token. The resulting token is intended to authorize a specific privileged operation without giving the agent standing broad access.