Okta AI Agent Service-to-Service Authentication

This lab demonstrates how to configure Okta for service-to-service AI Agent authentication without user context, using a machine-to-machine application, Token Exchange, ID-JAG, and a Custom Authorization Server to obtain delegated access to protected resources.

Register the AI Agent

The AI Agent is registered in Okta as a non-human identity and configured for machine-to-machine access only.

Directory > AI Agents > Register AI Agent > Register manually

For this lab, the External ID maps the Okta AI Agent record to the Agent identity created in Microsoft Copilot Studio.

User Access

User access is disabled because this lab implements a machine-to-machine authentication pattern without interactive user context.

Owner

The owner provides administrative accountability for the Agent and does not participate in the OAuth authentication flow.

Configure Client Registration

Directory > AI Agents > “Secure Resource Agent” > Client registration

For this lab we used – Authentication method: Client Secret

Generate a Client Secret for the AI Agent, store it securely, and activate.

The Agent client credentials are used when the Agent authenticates to Okta during the Token Exchange and ID-JAG flow.

Create the M2M Service Application

Applications > Applications > Create App Integration > API Services

This service application represents the machine identity that initiates the AI Agent authentication flow.

Save the Client ID and Client Secret. These credentials are used to request the initial subject token.

Configure the Custom Authorization Server

This authorization server defines the scopes, access policies, and token endpoint used throughout the service-to-service AI Agent flow.

Security > API > Add Authorization Server

Create scopes

Security > API > Authorization Servers > “Secure Resource Access” > Scopes > Add Scope

This scope represents the permission the AI Agent will request and carry through the delegated token flow.

Create the M2M Access Policy

Security > API > Authorization Servers > “Secure Resource Access > Access Policies” > Add New Access Policy

This policy allows the M2M service to obtain the initial subject token using client_credentials.

Use:

Name: Secure Resource Invocation Policy
Client: Secure Resource Invocation

Then add a rule:

Rule name: M2M Client Credentials
Grant type: Client Credentials
Scopes:
agent:invoke

Create the AI Agent JWT Bearer Policy

Security > API > Authorization Servers > “Secure Resource Access” > Access Policies > Add New Access Policy

This policy allows the AI Agent to exchange the ID-JAG for the final bearer token issued by the Custom Authorization Server.

Use:

Name:
Secure Resource Agent Policy
Client:
Secure Resource Agent

Then add a rule:

Rule name: AI Agent ID-JAG Exchange
Grant type: JWT Bearer
Scopes:
agent:invoke

AI Agent: Final Configuration

Machine access

Directory > AI Agents > “Secure Resource Agent” > Machine access > Configure

Machine Access defines which service can invoke the AI Agent and establishes the delegation relationship used for the initial subject token.

The resource URL becomes the audience for the initial M2M subject token and must match the value used in the resource parameter during client_credentials.

Add the M2M Caller

Adding the M2M application as a caller authorizes the service to initiate delegated access through the AI Agent.

Under Machine access, select Add caller.

Caller type: Application or service
Application: Secure Resource Invocation

Resource Connection

Directory > AI Agents > Secure Resource Agent > Resource connections > Add resource connection

The Resource Connection defines which protected authorization server and scopes the AI Agent can request through the delegated authentication flow.

Resource type: Authorization server
Authorization server:
Secure Resource Access
Scopes:
agent:invoke

Select the scopes that the AI Agent is allowed to request when exchanging the ID-JAG for access to the protected resource.

This connection authorizes the AI Agent to request agent:invoke access against the Custom Authorization Server during the ID-JAG token exchange.

Activate the AI Agent

Activate the AI Agent after its client registration, Machine Access, and Resource Connection are configured.

Test the Service-to-Service Token Flow

Request the Subject Token

curl --request POST \
--url 'https://<OKTA_DOMAIN>/oauth2/<CUSTOM_AS_ID>/v1/token' \
--user '<M2M_CLIENT_ID>:<M2M_CLIENT_SECRET>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=agent:invoke' \
--data-urlencode 'resource=https://ai-agent'

Expected result: T1 = Subject Access Token

The M2M service first requests a subject access token from the Custom Authorization Server using the client_credentials grant.

Exchange T1 for an ID-JAG

The AI Agent exchanges the initial subject token against the Okta Org Authorization Server to obtain an ID-JAG containing the delegated authorization context.

curl --request POST \
--url 'https://<OKTA_DOMAIN>/oauth2/v1/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'client_id=<AI_AGENT_CLIENT_ID>' \
--data-urlencode 'client_secret=<AI_AGENT_CLIENT_SECRET>' \
--data-urlencode 'subject_token=<T1>' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:id-jag' \
--data-urlencode 'audience=https://<OKTA_DOMAIN>/oauth2/<CUSTOM_AS_ID>' \
--data-urlencode 'scope=agent:invoke'

The audience points to the Custom Authorization Server issuer. The Machine Access resource parameter used when obtaining T1 is not sent in this request.

Expected result: T2 = ID-JAG

Exchange the ID-JAG for the Final Access Token

The AI Agent presents the ID-JAG to the Custom Authorization Server using the JWT Bearer grant to obtain the final bearer access token.

curl --request POST \
--url 'https://<OKTA_DOMAIN>/oauth2/<CUSTOM_AS_ID>/v1/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
--data-urlencode 'client_id=<AI_AGENT_CLIENT_ID>' \
--data-urlencode 'client_secret=<AI_AGENT_CLIENT_SECRET>' \
--data-urlencode 'assertion=<ID_JAG>'

Do not send scope in this request. The delegated scope context is already carried through the ID-JAG.

Expected result: T3 = Final Bearer Access Token

This final token is the one presented to the protected resource

Final Token and Delegation Claims

The final access token preserves both the original service identity and the AI Agent acting on its behalf.

{
"iss": "https://<OKTA_DOMAIN>/oauth2/<CUSTOM_AS_ID>",
"aud": "https://ai-agent",
"scp": [
"agent:invoke"
],
"sub": "<M2M_CLIENT_ID>",
"sub_profile": "service",
"act": {
"sub": "<AI_AGENT_CLIENT_ID>",
"sub_profile": "ai_agent",
"act": {
"sub": "<M2M_CLIENT_ID>",
"sub_profile": "service"
}
}
}

The top-level sub represents the original M2M service, while act.sub identifies the AI Agent as the immediate actor. The nested actor chain preserves the originating service for delegation and audit context.

This lab demonstrates a complete non-interactive AI Agent authentication pattern in Okta using client_credentials, Token Exchange, ID-JAG, and the JWT Bearer grant.

Comments

Leave a comment