Zero-Trust Agents: Adding Identity and Access to Multi-Agent Workflows
July 8, 2025
Understanding Azure Hybrid Benefit
July 8, 2025A day ago, I was working on a service request where our customer got the following error message:
{“response”:{“status”:{“http”:{“code”:401,”description”:””}},”headers”:{“Date”:”Mon, 07 Jul 2025 19:36:30 GMT”,”Content-Length”:”297″,”Content-Type”:”application/json”,”apim-request-id”:”cabfb91a-5ede-4371-91d5-XXX”,”x-ms-client-request-id”:”Not-Set”,”strict-transport-security”:”max-age=31536000; includeSubDomains; preload”,”x-content-type-options”:”nosniff”}},”result”:{“error”:{“code”:”PermissionDenied”,”message”:”The principal `XXXX-YYYY-4d9e-8e70-13c98fb84e7a` lacks the required data action `Microsoft.CognitiveServices/accounts/OpenAI/deployments/chat/completions/action` to perform `POST /openai/deployments/{deployment-id}/chat/completions` operation.”}}}.
Following I would like to share my experience resolving this issue.
The first thing was to try reproducing the issue in our lab. So, I began integrating Azure OpenAI with Azure SQL Database to perform sentiment analysis based on fictitious feedback.
I created a SQL table called CustomersInfo which contains fictitious customer feedback:
CREATE TABLE CustomersInfo (
CustomerID INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(100),
Feedback NVARCHAR(MAX),
Sentiment NVARCHAR(20) NULL
);
INSERT INTO CustomersInfo (Name, Feedback)
VALUES
(‘Anna’, ‘The product arrived damaged and no one responded to my messages.’),
(‘John’, ‘I loved the service, it was fast and the product is excellent.’),
(‘Emily’, ‘It was okay, but I think packaging could be better.’),
(‘David’, ‘I will never buy here again, terrible service.’),
(‘Sophia’, ‘Everything was perfect, thank you for the follow-up.’),
(‘Michael’, ‘Delivery time was average, but the product did not meet expectations.’),
(‘Laura’, ‘Great overall experience, I would recommend it.’),
(‘James’, ‘I expected more quality for the price I paid.’),
(‘Isabella’, ‘Easy to order, great customer support.’),
(‘Robert’, ‘I didn’t like it, but at least they gave me a refund.’);
I configured Azure OpenAI and permissions by creating an Azure OpenAI resource
- Endpoint: https://openaiexample.openai.azure.com
- Model: gpt-4
- Roles granted to the EntraID user that is connecting to the database:
- Cognitive Services OpenAI User
- Cognitive Services User
I then enabled the SQL database to call the OpenAI endpoint using Managed Identity:
CREATE DATABASE SCOPED CREDENTIAL [https://openaiexample.openai.azure.com]
WITH IDENTITY = ‘Managed Identity’,
SECRET = ‘{“resourceid”:”https://cognitiveservices.azure.com”}’;
DECLARE @response NVARCHAR(MAX);
DECLARE Payload NVARCHAR(MAX) =
‘{
“messages”: [
{ “role”: “system”, “content”: “You are a helpful assistant.” },
{ “role”: “user”, “content”: “Classify the sentiment of the following customer feedback as Positive, Negative, or Neutral. Feedback: The product arrived damaged and no one responded to my messages.” }
],
“max_tokens”: 50
}’;
EXEC sp_invoke_external_rest_endpoint
@url = ‘https://openaiexample.openai.azure.com/openai/deployments/gpt-4.1-jmjurado/chat/completions?api-version=2025-01-01-preview’,
@method = ‘POST’,
@credential = [https://openaiexample.openai.azure.com],
Payload = Payload,
@response = @response OUTPUT;
DECLARE @json NVARCHAR(MAX) = @response;
SELECT
JSON_VALUE(c.value, ‘$.message.content’) AS Respuesta
FROM
OPENJSON(@json, ‘$.result.choices’) AS c;
To encapsulate this logic, we created a stored procedure:
CREATE OR ALTER PROCEDURE AnalizarSentimiento
@Texto NVARCHAR(MAX),
@Sentimiento NVARCHAR(50) OUTPUT
AS
BEGIN
DECLARE @response NVARCHAR(MAX);
DECLARE Payload NVARCHAR(MAX) =
‘{
“messages”: [
{ “role”: “system”, “content”: “You are a helpful assistant.” },
{ “role”: “user”, “content”: “Classify the sentiment of the following customer feedback as Positive, Negative, or Neutral. Feedback: ‘ + @Texto + ‘” }
],
“max_tokens”: 50
}’;
EXEC sp_invoke_external_rest_endpoint
@url = ‘https://openaiexample.openai.azure.com/openai/deployments/gpt-4.1-jmjurado/chat/completions?api-version=2025-01-01-preview’,
@method = ‘POST’,
@credential = [https://openaiexample.openai.azure.com],
Payload = Payload,
@response = @response OUTPUT;
SELECT @Sentimiento = JSON_VALUE(c.value, ‘$.message.content’)
FROM OPENJSON(@response, ‘$.result.choices’) AS c;
END;
Now, I’m ready to execute the procedure and retrieve the data:
DECLARE @Sentimiento NVARCHAR(50);
EXEC AnalizarSentimiento
@Texto = ‘The product arrived damaged and no one responded to my messages.’,
@Sentimiento = @Sentimiento OUTPUT;
SELECT @Sentimiento AS Resultado;
However, I got the following result {“response”:{“status”:{“http”:{“code”:401,”description”:””}},”headers”:{“Date”:”Mon, 07 Jul 2025 19:36:30 GMT”,”Content-Length”:”297″,”Content-Type”:”application/json”,”apim-request-id”:”cabfb91a-5ede-4371-91d5-XXX”,”x-ms-client-request-id”:”Not-Set”,”strict-transport-security”:”max-age=31536000; includeSubDomains; preload”,”x-content-type-options”:”nosniff”}},”result”:{“error”:{“code”:”PermissionDenied”,”message”:”The principal `XXXX-YYYY-4d9e-8e70-13c98fb84e7a` lacks the required data action `Microsoft.CognitiveServices/accounts/OpenAI/deployments/chat/completions/action` to perform `POST /openai/deployments/{deployment-id}/chat/completions` operation.”}}}
Analyzing the error message, it appears that principal ID that I’m using for this Managed Identity to perform the call has not access to the endpoint. Even adding the client_id keyword didn’t resolve the issue.
CREATE DATABASE SCOPED CREDENTIAL [https://openaiexample.openai.azure.com]
WITH IDENTITY = ‘Managed Identity’,
SECRET = ‘{“client_id”:”YYYYY-ZZZZ-4b75-XXX-9ab8e8c14c1e”, “resourceid”:”https://cognitiveservices.azure.com”}’;
After several troubleshooting steps and by analyzing the error message, I identified that the client_id from the error message referenced in the response belongs to the system-assigned managed identity of the Azure SQL Server. Once I granted the necessary permissions to this identity, I was able to connect and successfully perform the operation.