This workflow corresponds to n8n.io template #12062 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "JxGoWSeBkNUoBvTc",
"meta": {
"templateId": "12062",
"templateCredsSetupCompleted": true
},
"name": "Control AI agent tool access with Port RBAC and Slack mentions",
"tags": [],
"nodes": [
{
"id": "5c4ceb24-7ccd-4b90-b2d7-14d723d08c74",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2448,
896
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "gpt-4o"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "132461b5-bd6f-4e68-b623-eaea4c536e3c",
"name": "Check permissions",
"type": "@n8n/n8n-nodes-langchain.code",
"notes": "A tool to check user's allowed tools and permissions",
"position": [
2880,
880
],
"parameters": {
"code": {
"supplyData": {
"code": "const { DynamicTool } = require(\"@langchain/core/tools\");\nconst connectedTools = await this.getInputConnectionData('ai_tool', 0);\nconst allowedTools = $input.item.json.allowed_tools;\n\nconst noTool = (tool) => {\n return new DynamicTool({\n name: tool.getName(),\n description: tool.description,\n func: async () => {\n return \"Tell the user 'You are not authorized to use this tool'.\";\n },\n });\n}\n\nreturn connectedTools.map(connectedTool => {\n const permissionGranted = allowedTools.includes(connectedTool.getName());\n return permissionGranted ? connectedTool : noTool(connectedTool);\n});"
}
},
"inputs": {
"input": [
{
"type": "ai_tool",
"required": true
}
]
},
"outputs": {
"output": [
{
"type": "ai_tool"
}
]
}
},
"typeVersion": 1
},
{
"id": "0a041ea8-f282-4120-8c4e-5e86df4373d2",
"name": "Set input",
"type": "n8n-nodes-base.set",
"position": [
2576,
624
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "9ea62c8f-984b-4c05-8e40-549d8035c4d3",
"name": "name",
"type": "string",
"value": "={{ $json.entity.identifier }}"
},
{
"id": "bf74b2c4-f0d1-458a-9044-5cb1b62722e6",
"name": "granted_roles",
"type": "array",
"value": "={{ $json.entity.relations.roles || [] }}"
},
{
"id": "e0f4d3d7-a916-43cb-a13d-e4453b0d1a3b",
"name": "allowed_tools",
"type": "array",
"value": "={{ $json.entity.properties.allowed_tools || [] }}"
},
{
"id": "26d0f442-4cd3-4930-a80b-fc471226dd36",
"name": "regions",
"type": "array",
"value": "={{ $('Get Regions from Port').item.json.entities.map(e => e.identifier) }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "5e15aaad-cf19-401b-a96a-97b9d028d177",
"name": "calculator",
"type": "@n8n/n8n-nodes-langchain.toolCalculator",
"position": [
2928,
1136
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a5c4e743-3894-45c7-a3b9-fbf42ac3eed1",
"name": "Unknown user",
"type": "n8n-nodes-base.if",
"position": [
2224,
496
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1d042f5b-ef39-4b9e-8d9c-900b39dbe3fb",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.ok }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "966a41d6-68b0-4629-84f3-0d925b6e88e3",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2800,
832
],
"parameters": {
"color": 7,
"width": 380,
"height": 220,
"content": "Uses list of allowed tools gathered from Port to check for permissions and replaces denied tools with a fixed instruction to return a message to the user."
},
"typeVersion": 1
},
{
"id": "9394b13a-262f-49fa-9b3e-99f1a17766a3",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2736,
560
],
"parameters": {
"color": 7,
"width": 380,
"height": 240,
"content": "AI agent with the instruction to always use the connected tools to respond to the user's request"
},
"typeVersion": 1
},
{
"id": "b9cf7ecf-5372-4e75-be15-eebbd79a458f",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2512,
560
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Collects input and formats it using required keys"
},
"typeVersion": 1
},
{
"id": "f307100d-d57d-42a8-bc1f-d93a6cc62a82",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
1120,
416
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Listens to messages directly sent to the Slack bot"
},
"typeVersion": 1
},
{
"id": "1be0d592-609b-45b6-9970-ad7d49f43e96",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
1952,
432
],
"parameters": {
"color": 7,
"width": 428,
"height": 240,
"content": "Checks if the user was found in Port"
},
"typeVersion": 1
},
{
"id": "e1f41863-12e5-4d35-8d6c-2f14884fb4ad",
"name": "Slack Trigger",
"type": "n8n-nodes-base.slackTrigger",
"position": [
1168,
496
],
"parameters": {
"options": {},
"trigger": [
"app_mention"
],
"channelId": {
"__rl": true,
"mode": "id",
"value": "channelId"
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "0204fb6e-05b1-46c4-8d09-d7b8a8666b01",
"name": "Send a message",
"type": "n8n-nodes-base.slack",
"position": [
2576,
384
],
"parameters": {
"text": "User not found in Port. Please contact your administrator.",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Slack Trigger').item.json.channel }}"
},
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "2b119903-8698-4d05-b6ed-d50ed3f40fd0",
"name": "Get user permission from Port",
"type": "n8n-nodes-base.httpRequest",
"position": [
2016,
496
],
"parameters": {
"url": "=https://api.port.io/v1/blueprints/_user/entities/{{ $('Get user\\'s slack profile').item.json.email }}",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $('Get Port access token').item.json.tokenType }} {{ $('Get Port access token').item.json.accessToken }}"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "76183718-358b-443d-af04-4b7cef4130b7",
"name": "Wikipedia",
"type": "@n8n/n8n-nodes-langchain.toolWikipedia",
"position": [
3056,
1152
],
"parameters": {},
"typeVersion": 1
},
{
"id": "4f99e983-0ca3-4e79-8e55-bf4745531bf0",
"name": "Create an incident in PagerDuty",
"type": "n8n-nodes-base.pagerDutyTool",
"position": [
3248,
1136
],
"parameters": {
"email": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Email', ``, 'string') }}",
"title": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Title', ``, 'string') }}",
"resource": "incident",
"operation": "create",
"serviceId": "YOUR_PD_SERVICE_ID",
"authentication": "apiToken",
"descriptionType": "auto",
"additionalFields": {},
"conferenceBridgeUi": {}
},
"credentials": {
"pagerDutyApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "60a35b13-fa61-4b86-91e6-3e0b849b930f",
"name": "Create a bucket in AWS S3",
"type": "n8n-nodes-base.awsS3Tool",
"position": [
2768,
1136
],
"parameters": {
"name": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('BucketName', ``, 'string') }}",
"resource": "bucket",
"additionalFields": {
"region": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Region', ``, 'string') }}"
}
},
"credentials": {
"aws": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "2a538a0c-3539-4511-8ea9-63002a6e7c2a",
"name": "Get user's slack profile",
"type": "n8n-nodes-base.slack",
"position": [
1392,
496
],
"parameters": {
"user": {
"__rl": true,
"mode": "id",
"value": "={{ $json.user }}"
},
"resource": "user",
"operation": "getProfile"
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "50b8dda9-26c5-41ac-8f47-a88f688d8b81",
"name": "Get Port access token",
"type": "n8n-nodes-base.httpRequest",
"position": [
1600,
496
],
"parameters": {
"url": "https://api.port.io/v1/auth/access_token",
"method": "POST",
"options": {},
"jsonBody": "{\n \"clientId\": \"REPLACE WITH CLIENT ID\",\n \"clientSecret\": \"REPLACE WITH CLIENT SECRET\"\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.3
},
{
"id": "bcad96f3-3279-465c-8378-08656dd71e88",
"name": "Chat Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
2640,
896
],
"parameters": {
"sessionKey": "={{ $json.name}}",
"sessionIdType": "customKey"
},
"typeVersion": 1.3
},
{
"id": "74000745-d221-4b0c-97fa-25b1f9c1a0f8",
"name": "Send output message",
"type": "n8n-nodes-base.slack",
"position": [
3184,
624
],
"parameters": {
"text": "={{ $json.output }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Slack Trigger').item.json.channel }}"
},
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "fffead23-643d-48ef-9e17-ca894ee75967",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
368,
240
],
"parameters": {
"width": 688,
"height": 576,
"content": "## AI Agent Access Control (Port + Slack)\n\nThis workflow adds role-based access control to AI agents. Users @mention the bot in Slack, and the workflow checks their permissions in Port before letting the agent use any tools.\n\n### How it works\n1. Slack trigger picks up @mentions and gets the user's email.\n2. Authenticates with Port and looks up the user in the _user blueprint.\n3. If the user exists, reads their allowed_tools array.\n4. The LangChain code node filters tools at runtime, swapping any unauthorized tool with a \"not authorized\" stub.\n5. AI agent runs with only permitted tools, then posts the response back to Slack.\n\n### Setup\n- [ ] Connect your Slack account and set the channel ID.\n- [ ] Add your OpenAI API key.\n- [ ] Get a free Port account at port.io.\n- [ ] Create the [rbac blueprints](https://docs.port.io/guides/all/implement-rbac-for-ai-agents-with-n8n-and-port/#set-up-the-port-data-model) in Port with an allowed_tools property (string array).\n- [ ] Add user entities with their email as identifier and allowed tools listed.\n- [ ] Replace the Port client ID and secret in the \"Get Port access token\" node.\n- [ ] Connect any tool credentials you want to use (PagerDuty, AWS, etc.).\n- [ ] Invite the bot to your Slack channel."
},
"typeVersion": 1
},
{
"id": "a5396cc8-81f7-454d-9e0f-fe5bf9d87652",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2800,
624
],
"parameters": {
"text": "={{ $('Slack Trigger').item.json.text }}",
"options": {
"systemMessage": "=You are a personal assistant. The name of the current user is \"{{ $json.name }}\"\nYou MUST only use the provided tools to process any user input. Never use general knowledge to answer questions. If you can't use a tool, tell the user why.\n\nBelow are the list of allowed tools for this user:\n{{ $json.allowed_tools }}\n\nRegions available to use when interacting with AWS: {{ $json.regions }}",
"returnIntermediateSteps": true
},
"promptType": "define"
},
"typeVersion": 1.8
},
{
"id": "076909ce-1054-41ef-8cf7-a3d85a56c493",
"name": "Get Regions from Port",
"type": "n8n-nodes-base.httpRequest",
"position": [
1792,
496
],
"parameters": {
"url": "https://api.port.io/v1/blueprints/region/entities",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $('Get Port access token').item.json.tokenType }} {{ $('Get Port access token').item.json.accessToken }}"
}
]
}
},
"typeVersion": 4.3
}
],
"active": false,
"settings": {
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "0cd92b77-2f77-46e0-aa63-5424cc642de2",
"connections": {
"AI Agent": {
"main": [
[
{
"node": "Send output message",
"type": "main",
"index": 0
}
]
]
},
"Set input": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Wikipedia": {
"ai_tool": [
[
{
"node": "Check permissions",
"type": "ai_tool",
"index": 0
}
]
]
},
"calculator": {
"ai_tool": [
[
{
"node": "Check permissions",
"type": "ai_tool",
"index": 0
}
]
]
},
"Chat Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Unknown user": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
],
[
{
"node": "Set input",
"type": "main",
"index": 0
}
]
]
},
"Slack Trigger": {
"main": [
[
{
"node": "Get user's slack profile",
"type": "main",
"index": 0
}
]
]
},
"Check permissions": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Get Port access token": {
"main": [
[
{
"node": "Get Regions from Port",
"type": "main",
"index": 0
}
]
]
},
"Get Regions from Port": {
"main": [
[
{
"node": "Get user permission from Port",
"type": "main",
"index": 0
}
]
]
},
"Get user's slack profile": {
"main": [
[
{
"node": "Get Port access token",
"type": "main",
"index": 0
}
]
]
},
"Create a bucket in AWS S3": {
"ai_tool": [
[
{
"node": "Check permissions",
"type": "ai_tool",
"index": 0
}
]
]
},
"Get user permission from Port": {
"main": [
[
{
"node": "Unknown user",
"type": "main",
"index": 0
}
]
]
},
"Create an incident in PagerDuty": {
"ai_tool": [
[
{
"node": "Check permissions",
"type": "ai_tool",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
awsopenAiApipagerDutyApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow implements role-based access control for AI agent tools using Port as the single source of truth for permissions. Different users get access to different tools based on their roles, without needing a separate permission database.
Source: https://n8n.io/workflows/12062/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
My workflow 7. Uses lmChatOpenRouter, slackTrigger, chatTrigger, agent. Event-driven trigger; 37 nodes.
Time Logging On Clockify Using Slack. Uses lmChatOpenAi, toolCalculator, toolHttpRequest, toolCode. Event-driven trigger; 16 nodes.
ClockifyBlockiaWorkflow. Uses lmChatOpenAi, toolCalculator, toolHttpRequest, toolCode. Event-driven trigger; 16 nodes.
This workflow simplifies time tracking for teams and agencies by integrating Slack with Clockify. It enables users to log, update, or delete time entries directly within Slack, leveraging an AI-powere
Slack-Ai-Assistant. Uses agent, lmChatOpenAi, memoryBufferWindow, slackTrigger. Event-driven trigger; 13 nodes.