Securing AI agents with PingOne using delegation and least privilege
PingOne PingGateway
Organizations are deploying AI-driven digital assistants to act on behalf of humans. To securely deploy these agents, treat them as first-class, non-human identities distinct from end users and enforce least-privilege access.
As an example scenario, consider a chatbot embedded in a banking app:
-
Customers use the chatbot to retrieve account balances and make transactions.
-
Acting on behalf of the customer, the chatbot accesses backend account resources.
-
To secure these interactions, PingOne issues tokens and exchanges them as necessary, based on the agent’s requested access and target resource.
-
PingGateway validates the tokens, acting as a security proxy in front of account resources.
You can find more details about the process flow in Securing Digital Assistants.
Goals
-
Secure a digital assistant to ensure the agent only performs authorized actions on behalf of the user.
-
Configure OAuth 2.0 token exchange to issue downscoped access tokens that preserve the chain of delegation using the
actclaim. -
Protect backend Model Context Protocol (MCP) servers and APIs using PingGateway.
Before you begin
To complete this use case, you’ll need:
-
Administrator access in a PingOne environment that’s configured to work with PingGateway. Learn more in PingGateway and PingOne in the PingGateway documentation.
-
PingGateway installed and configured with server-side SSL (TLS). Learn more in MCP security gateway and Configuring PingGateway for TLS (server-side) in the PingGateway documentation. The sample application isn’t required for this use case.
Example admin.json file
This is an example
.openig/config/admin.jsonconnector for SSL. The user ismacuserand the secrets folder/Users/macuser/.openig/secretscontains the self-signed certificate for ig.example.com.{ "adminConnector": { "host": "localhost", "port": 8085 }, "connectors": [ { "port": 8080 }, { "port": 8443, "tls": "ServerTlsOptions-1" } ], "streamingEnabled": true, "heap": [ { "name": "ServerTlsOptions-1", "type": "ServerTlsOptions", "config": { "keyManager": { "type": "SecretsKeyManager", "config": { "signingSecretId": "key.manager.secret.id", "secretsProvider": "ServerIdentityStore" } } } }, { "name": "ServerIdentityStore", "type": "FileSystemSecretStore", "config": { "format": "PLAIN", "directory": "/Users/macuser/.openig/secrets", "suffix": ".pem", "mappings": [{ "secretId": "key.manager.secret.id", "format": { "type": "PemPropertyFormat" } }] } } ] } -
A deployed MCP server capable of exposing tools the agent will access, such as
banking:read_balanceandbanking:transfer. You can use the sample MCP and agent software described in MCP security gateway preparation in the PingGateway documentation.The sample MCP and agent software is for a weather application, but you can adapt them for this use case if you don’t already have an MCP server or agent.
Tasks
Adding custom resources
Add custom resources that represent the services the agent is allowed to access. You can find more details in Adding a custom resource in the PingOne documentation.
You’ll add two resources that serve different purposes in the delegation flow:
-
The test resource represents the backend service that the agent calls through PingGateway. Its attributes map the human user into
suband, when delegation is valid, copy the user’smay_actintoactso the backend can see that this agent is acting on behalf of this user for a specific audience. -
The agent resource represents the agent itself and the delegation relationship between the user and the agent, populated in
may_act. This relationship establishes who is allowed to act (which client ID the user delegated to) when the agent calls the backend. The backend resource usesmay_actinformation to enforce what the agent is allowed to do with protected resources.
|
Settings for your own custom resources might vary from those used as examples in this use case. |
-
In the PingOne admin console, go to Applications > Resources and click the icon.
-
Add the test resource:
-
Name the resource
test, enterhttps://ig.example.com:8443/mcpfor the Audience, and click Next. -
Click the Gear icon () next to the
subattribute, enter the expression#root.context.requestData.subjectToken.sub, and click Save.Attribute mappings can pass complex information to applications through an access token. This expression copies the user’s
sub(subject) claim from the incoming subject token to let downstream services know which human the agent is acting on behalf of. Learn more about expressions in Using the expression builder in the PingOne documentation. -
Click + Add and name the attribute
act. -
Click next to the
actattribute, enter the following expression, and click Save.(#root.context.requestData.subjectToken.may_act.sub == #root.context.requestData.actorToken.client_id)?#root.context.requestData.subjectToken.may_act:nullThis expression copies the
may_actdelegation from the user’s token intoactwhen the delegated client ID matches the current agent’sclient_id. When there isn’t a match,actis left empty to prevent unauthorized delegation. -
Click Next, then click + Add Scope.
-
Name the scope
testand click Save. -
Click the test resource to display the Overview tab.
-
Copy the Resource ID and Client Secret and store them somewhere convenient. You’ll use them for PingGateway configuration.
-
Close the test resource.
-
-
Add the agent resource:
-
Click the icon.
-
Name the resource
agent, keep the defaultagentfor the Audience, and click Next. -
For the
subattribute, select Username in the PingOne Mappings list. -
Click + Add and name the attribute
may_act. -
Click next to the
may_actattribute, enter the following expression, and click Save.(#root.context.requestData.grantType == "client_credentials")?null:({ "sub": #root.context.appConfig.clientId })This expression omits
may_actfor pureclient_credentialsrequests, and otherwise builds a simple delegation object that records this agent’sclient_idas the subject ofmay_act. -
Click Next, then click + Add Scope.
-
Name the scope
agentand click Save.
-
Adding a consent agreement
Add an agreement for the user’s consent for agent actions. You can find more details in Agreements and Adding an agreement in the PingOne documentation.
-
In the PingOne admin console, go to User Experience > Agreements and click the icon.
-
Name the agreement
Agent Consentand enter a description such asConsent for a digital assistant agent. -
For Reconsent Every, select Number of Days and keep the default
180. -
Click Save.
-
Add a language:
-
Click Edit Localized Content and click the Languages + icon.
-
For Language, select English (en), and click Save.
-
In Localized Agreement Content, enter
I consent to allow digital assistants created by MyCompany to act on my behalf, then click Save.The language is enabled by default.
-
Close the language.
-
-
On the Agreements page, click the toggle to enable the Agent Consent agreement.
Adding an authentication policy
Add an authentication policy for the agent. The authentication policy ensures that users explicitly agree to let a digital assistant act on their behalf before any delegated access is granted. By prompting for consent during sign-on, PingOne can issue user tokens that record this approval, which token exchange uses later to build the act and may_act claims. This keeps delegation transparent, auditable, and aligned with least-privilege and human-in-the-loop (HITL) requirements. Learn more in Adding an authentication policy in the PingOne documentation.
-
In the PingOne admin console, go to Authentication > Authentication and click + Add Policy.
-
Name the policy
Agent-Consent-Login. -
For Step Type, select Login, then click + Add step.
-
For Step Type, select Agreement Prompt.
-
Select the Agent Consent terms of service agreement and click Save.
Adding an AI agent
Register the agent as an identity in PingOne and configure its authentication settings, such as scopes for the actions the agent can perform. You can find more details in Managing AI agents in the PingOne documentation.
In our example scenario, the agent will interact with an application hosted locally, on the 3000 port.
-
In the PingOne admin console, go to AI Agents and click the icon.
-
Name the agent
MCP Tutorial, enter a description such asMCP agent for PingGateway tutorial, and click Save. -
On the Configuration tab, click the Pencil icon () to edit configuration settings.
Keep all of the default settings.
-
For Grant Type, select Client Credentials, Refresh Token, and Token Exchange.
These settings allow the agent to authenticate autonomously and exchange user tokens for delegation tokens at runtime.
-
In Redirect URIs, enter
http://localhost:3000/callback. -
Click Save.
-
-
On the Resources tab, click the Pencil icon () to edit resource settings:
-
Select the agent and test scopes.
These scopes ensure that the agent can get its own access token and exchange user tokens for MCP access.
-
Click Save.
-
-
On the Policies tab, click + Add Policies.
-
Select the Agent-Consent-Login policy.
-
Click Save.
-
-
Click the toggle at the top of the panel to enable the agent.
-
On the Overview tab, copy the Client ID and store it somewhere convenient. You’ll use it when restarting the MCP agent.
Result
You’ve successfully configured PingOne to act as the authorization server.
Configuring PingGateway
PingGateway serves as the enforcement point that protects the MCP server and validates tokens.
-
In the
admin.jsonfile for PingGateway, enable streaming:"streamingEnabled": trueThis is already enabled in the example
admin.jsonfile you set up as a prerequisite. PingGateway requires this setting for server-side events (SSE), an MCP transport option. Learn more about server-side events (SSE) in the Mozilla documentation. -
Export a RESOURCE_SECRET_ID environment variable with the base64 encoded client secret for the PingOne test resource:
bash: export RESOURCE_SECRET_ID=$(echo -n "<Resource client secret>" | base64)Make sure you don’t include a new line.
-
Add the following route to PingGateway:
- Linux
-
$HOME/.openig/config/routes/mcp.json - Windows
-
%appdata%\OpenIG\config\routes\mcp.json
Route configuration
Enter the
PingOne environment IDandPingOne test resource IDfor your deployment.{ "name": "mcp", "condition": "${find(request.uri.path, '^/mcp')}", "properties": { "pingOneEnvID": "https://auth.pingone.com/<PingOne environment ID>", "pingOneResourceID": "<PingOne test resource ID>", "gatewayUrl": "https://ig.example.com:8443", "mcpServerUrl": "http://localhost:8000" }, "baseURI": "&{mcpServerUrl}", "heap": [ { "name": "SystemAndEnvSecretStore-1", "type": "SystemAndEnvSecretStore" }, { "name": "AuditService", "type": "AuditService", "config": { "eventHandlers": [ { "class": "org.forgerock.audit.handlers.json.JsonAuditEventHandler", "config": { "name": "json", "logDirectory": "&{ig.instance.dir}/audit", "topics": ["access", "mcp"] } } ] } }, { "name": "rsFilter", "type": "OAuth2ResourceServerFilter", "config": { "requireHttps": false, "scopes": ["test"], "accessTokenResolver": { "type": "TokenIntrospectionAccessTokenResolver", "config": { "endpoint": "&{pingOneEnvID}/as/introspect", "providerHandler": { "type": "Chain", "config": { "filters": [ { "type": "HttpBasicAuthenticationClientFilter", "config": { "username": "&{pingOneResourceID}", "passwordSecretId": "resource.secret.id", "secretsProvider": "SystemAndEnvSecretStore-1" } } ], "handler": "ForgeRockClientHandler" } } } } } } ], "handler": { "type": "Chain", "config": { "filters": [ { "type": "McpAuditFilter", "config": { "auditService": "AuditService" } }, { "type": "UriPathRewriteFilter", "config": { "mappings": { "/mcp": "/" } } }, { "type": "McpProtectionFilter", "config": { "resourceId": "&{gatewayUrl}/mcp", "authorizationServerUri": "&{pingOneEnvID}/as", "resourceServerFilter": "rsFilter", "supportedScopes": ["test"], "resourceIdPointer": "/aud/0" } }, { "type": "McpValidationFilter", "config": { "acceptedOrigins": ".*" } } ], "handler": { "type": "ReverseProxyHandler", "config": { "soTimeout": "20 seconds" } } } } }Features of the sample route:
-
This route uses a secret obtained from an environment variable.
-
PingGateway acts as an OAuth 2.0 resource server (RS) when protecting the sample MCP server.
-
The McpAuditFilter audits MCP requests. PingGateway records MCP audit events in an
audit/mcp.audit.jsonfile. -
The UriPathRewriteFilter sends the request to the root resource of the MCP server. The MCP server expects requests at
/. -
The McpProtectionFilter uses the RS configuration, extending it for MCP.
-
PingGateway validates MCP requests with an McpValidationFilter.
-
The ReverseProxyHandler uses a long
"soTimeout"setting to accommodate an MCP agent receiving few or infrequent SSE updates.
-
Restart PingGateway to apply the route changes.
-
(Optional) Add throttling or fine-grained access control as necessary to meet your security requirements.
This simple route doesn’t include those features.
-
Check the PingGateway log to verify the route loads successfully.
Result
You’ve successfully configured PingGateway to protect the sample MCP server.
Start the MCP agent
-
In the directory where you unpacked the sample MCP agent, export the AI agent secret as an environment variable called AGENT_SECRET.
-
Start the sample MCP agent again with the added option
–client id.You’ll need the client ID from the MCP Tutorial agent. Point it to the PingGateway route for MCP requests.
bash-3.2$ export AGENT_SECRET=yjS0IWMX9LS91... bash-3.2$ python3 sample-mcp-agent.py --client-id <PingOne AI agent client ID> --mcp-server-url https://ig.example.com:8443/mcp
Result
You’ve successfully started the sample MCP agent.
Validation
To validate this use case, you’ll confirm the full flow:
-
The agent can discover tools from the MCP server.
-
The agent can open a browser and redirect to PingOne.
-
The user can sign on and grant consent.
-
The agent can make a tool call and receive a response.
-
PingGateway logs show the MCP request with a valid act claim.
With PingGateway protecting the MCP server, the sample MCP agent directs you to the authorization server to sign on as an end user and authorize access to make MCP requests.
-
Allow the agent to open a browser automatically, or:
-
Select
‘n’. -
Copy the authorization URL that the sample MCP agent displays in your terminal.
-
Paste the URL into a browser.
The URL should look similar this:
https://auth.pingone.com/082e56dd-1a0f-4e1d-a28e-77d51ecf1705/as/authorize?response_type=code&client_id=3fffb1bf-106c-4449-a9bd-df6fa76d8f41&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&state=…
-
-
Sign on as the test user and consent to allow a digital assistant (if asked) before you close the browser tab.
The following commands are available in the terminal where the sample MCP agent is running:
[INFO] Discovered tools [https://ig.example.com:8443/mcp]: [INFO] - geocode: Returns a list of objects containing city name, latitude, longitude, country, admin1 (region), and timezone for each matching city [INFO] - forecast_daily: Returns a multi-day weather forecast for a given location [INFO] - forecast_periods: Returns weather forecasts for each representative period of the current day [INFO] - forecast_hourly: Returns an hourly weather forecast for the current day [INFO] - weather_at_time: Returns the forecasted weather for a specific time at a given location Enter your message (or 'exit|quit|q'):
-
Enter a prompt and get a response from the MCP server through PingGateway, then exit the agent.
The following example uses the forecast_daily tool to get the daily forecast for Tokyo:
Enter your message (or 'exit|quit|q'): What is the daily forecast for Tokyo? Agent: The daily forecast for Tokyo is: <MCP server response with forecast details> Enter your message (or 'exit|quit|q'): exit User requested exit. Goodbye!
The following tokens are used in the transaction:
-
Actor token: The AI agent’s access token.
Agent token (actor token) example
{ "aud": [ "agent" ], "client_id": "adeeb901-7b64-453d-92bc-059bcbcc5958", "env": "4631af52-7ec7-48cf-848f-48cf8ca8e1ab", "exp": 1774043329, "iat": 1774039729, "iss": "https://auth.pingone.com/4631af52-7ec7-48cf-848f-48cf8ca8e1ab/as", "jti": "722238d2-4748-4681-889d-7fe7a321b037", "org": "84c4c0c0-0ca2-43b4-a62c-d830882855bc", "p1.rid": "722238d2-4748-4681-889d-7fe7a321b037", "scope": "agent" } -
Subject token: The end user’s access token. The
may_actclaim asserts that the agent is allowed to act on the user’s behalf.User token (subject token) example
{ "acr": "Agent-Consent-Login", "aud": [ "agent" ], "auth_time": 1774039361, "client_id": "adeeb901-7b64-453d-92bc-059bcbcc5958", "env": "4631af52-7ec7-48cf-848f-48cf8ca8e1ab", "exp": 1774043333, "iat": 1774039733, "iss": "https://auth.pingone.com/4631af52-7ec7-48cf-848f-48cf8ca8e1ab/as", "jti": "3121e22a-5958-4bf1-a369-ba296b444930", "may_act": { "sub": "adeeb901-7b64-453d-92bc-059bcbcc5958" }, "org": "84c4c0c0-0ca2-43b4-a62c-d830882855bc", "p1.userId": "cf025143-501a-4d70-9c80-95d69121d7b3", "scope": "agent", "sid": "2dd6d4a4-0b59-4c99-8a57-4c189f09b24f", "sub": "demouser" } [INFO] Exchanging actor token and subject token for a new mcp token (scope='test') -
MCP token: An on-behalf-of token obtained through PingOne token exchange. The
actandsubclaims assert that this token is used by the agent on behalf of the test user.Exchanged on-behalf-of token (mcp token) example
{ "acr": "Agent-Consent-Login", "act": { "sub": "adeeb901-7b64-453d-92bc-059bcbcc5958" }, "aud": [ "https://ig.example.com:8443/mcp" ], "auth_time": 1774039361, "client_id": "adeeb901-7b64-453d-92bc-059bcbcc5958", "env": "4631af52-7ec7-48cf-848f-48cf8ca8e1ab", "exp": 1774043333, "iat": 1774039733, "iss": "https://auth.pingone.com/4631af52-7ec7-48cf-848f-48cf8ca8e1ab/as", "jti": "e1e90599-b09f-4549-8ea9-6a297490e53c", "org": "84c4c0c0-0ca2-43b4-a62c-d830882855bc", "p1.userId": "cf025143-501a-4d70-9c80-95d69121d7b3", "scope": "test", "sid": "2dd6d4a4-0b59-4c99-8a57-4c189f09b24f", "sub": "demouser" }You can decode tokens using your preferred JSON Web Token (JWT) viewer. Don’t paste sensitive tokens from production environments into third-party sites.
If your delegation token is missing the
actclaim, verify:-
PingOne resource attribute mappings for act are configured correctly.
-
The subject token contains a
may_actclaim. -
The agent’s
client_idmatchesmay_act.sub.
-
-
Check the PingGateway log for additional details about the MCP request.
Result
You’ve successfully validated that PingGateway can protect the MCP server.
Troubleshooting
If you encounter issues while configuring this use case, use the following information to help resolve common problems.
Token exchange fails and act claim is null
Verify that the attribute expressions in your custom resources accurately match client IDs and that the act claim is properly mapped in the token.
PingGateway drops MCP traffic
Verify that streamingEnabled is set to true in your admin.json file. PingGateway requires this setting for SSE utilized by the MCP protocol.
Gateway routing errors
Verify that the UriPathRewriteFilter is properly stripping the /mcp path down to / before it reaches the backend MCP server, as most MCP servers expect requests at the root.
Consent prompt issues
If the user doesn’t receive an agent consent prompt, verify these things in PingOne:
-
The Agent-Consent-Login policy is assigned to the agent on the Policies tab in AI Agents.
-
The Redirect URI is correct on the Configuration tab in AI Agents.
If the user receives a consent prompt on every sign-on, verify these things in PingOne:
-
The Reconsent Every interval isn’t too short. You can edit the interval on the Overview tab in User Experience > Agreements.
-
Scopes aren’t changing between sessions.
What’s next
Add fine-grained authorization policies to secure agent actions using real-time contextual signals. Learn more about authorization in the PingOne Authorize documentation.