A Slack AI agent that always acts as one shared bot identity is easy to install and hard to defend. IT and digital-workplace admins feel this when a support lead asks the agent to open a private channel, a knowledge owner asks it to edit a restricted page, or a security review asks who authorized a GitHub write and the only honest answer is "the bot." Shared xoxb- tokens collapse privilege and authorship into one opaque principal.
If you own Slack-connected agents that search knowledge, draft support answers, touch ticketing, or call developer tools, use this as an identity and delegation control plan. It covers bot versus user tokens, per-user OAuth, live grant checks at tool time, token isolation from the model, audit fields, failure modes, and a pilot checklist. Use it when the agent must act with the requesting user's grants rather than workspace-wide bot privileges.
Why shared bot tokens break multi-user agents
Slack's own token model separates identities. Slack Developer Docs on Tokens describe bot tokens for the app and user tokens that let an app work directly on behalf of users when necessary. Slack Developers' guide to bot and user tokens states the operational difference clearly: messages posted with a bot token appear under the app's name, while a user token makes Slack treat the action as if that person performed it. Bot tokens also persist independently of any single user account, which is useful for durable app features and dangerous when every agent tool call rides the same broad bot scopes.
Many agent tutorials wire that durable bot identity first. One install, one token, every teammate mentions the same app. The model then reads channels the bot can see, posts as the bot, and opens connected systems with whatever shared credential the integration stored. When a second person shows up, authorship and access no longer match reality.
freeCodeCamp's handbook on per-user OAuth for AI agents documents the failure mode in production language: every GitHub issue says the bot opened it, every Slack reply comes from the bot, and the honest answer to "why does this issue exist" becomes "an agent filed it for somebody, and we can't tell who." The same handbook warns that Slack returns the bot at top-level access_token and the consenting person at authed_user.access_token. Storing the wrong field is a one-character mistake that silently restores shared-bot behavior.
Practitioner threads match the same pattern. A Reddit r/Slack discussion on shared bots describes service-account authorship, missing requester context, and per-user authentication as the fix so connected tools show the right person's name. Treat those as community reports, not measured demand. The practical lesson is blunt: shared bot tokens reduce install friction and erase attribution.
Security guidance also treats the agent as an identity, not only as a poster. NHIMG's FAQ on Slack-connected AI agents as identities argues that webhooks, bot tokens, and MCP-backed apps should be treated as non-human identities with ownership, scope, and revocation, not as ad hoc convenience features. NHIMG's companion FAQ on Slack-native AI workflows focuses on workflows that can trigger real actions. Your control plan has to answer who the agent is acting as before the tool call lands.
Separate the three principals on every run
Define the identity model before you pick scopes. Every agent run has at least three principals:
- Requester: the Slack user who mentioned the agent or owns the scheduled job.
- Acting principal: the token identity that Slack and connected APIs will actually see (
xoxb-bot orxoxp-user, plus any downstream OAuth principal such as GitHub). - App identity: the Slack app record, install, and admin policy surface.
Interactive mentions should set the acting principal from the requester's grants for user-scoped reads and writes. Unattended schedules need an explicit principal named in the schedule contract, not "whoever last talked to the agent." That schedule case overlaps rank 86's fixed acting identity for delivery-safe cron jobs; this article keeps the focus on replacing shared bot privilege for interactive multi-user work and for any tool call that must inherit a person's grants.
Slack's Governance and trust guide tells builders to log which channels were accessed, what actions were taken, who triggered the request, and what model was used. That baseline only works if your runtime knows the requester and the acting principal as separate fields. Logging "bot did something" is not an audit trail.
Kipwise Agent fits this model as a thread-native work agent. A teammate can mention it once and continue with ordinary replies. Applicable actions use the requesting user's permissions. It cites sources it opened, refuses to guess, and keeps consequential writes explicit. Mention those product behaviors once here, then design the permission controls around the same rules.
Choose bot token or user token per tool call
Avoid one token type for the whole agent. Choose per capability.
Use a bot token when the app should speak as itself: status notifications, "I started the run" acknowledgements, public summaries that must not impersonate a person, or events that depend on channels the bot was invited to. Bot scopes stay durable when people leave. That durability is useful for app features and risky for privileged tools.
Use a user token when the action must inherit a person's visibility or authorship: reading conversations the bot was never invited to, posting as the person, opening private knowledge the person can see, or writing to a connected system where audit expects a human principal. Slack's token article is explicit that user tokens grant access tied to a real person's identity and permissions, and that you should request them only when a feature genuinely requires it.
A practical decision rule for Slack AI agents:
- If the tool only needs app-visible public context and should post as the app, prefer bot token.
- If the tool reads or writes anything the requester might not be allowed to see or change, require user token (or an explicit delegated service role with its own review).
- If the tool creates an artifact in another system (issue, ticket, doc edit), default to the requester's OAuth grant for that provider.
- If you cannot name the acting principal before the call, refuse the call.
freeCodeCamp's handbook shows the Slack parameter split in one line: scope requests bot permissions, user_scope requests user permissions. An agent that needs per-user behavior must request and store the user grant on purpose. Hoping the install "just works" with only bot scopes will not produce requester-bound reads.
Generic Slack agent tutorials often stop at install and chat.postMessage. Admins still need an explicit rule to select token type per tool call so workspace-wide bot privilege is not the default for every read and write.
Store per-user grants behind a gateway, not in the model
Per-user OAuth adds a storage requirement. Each user consents once. Your system must keep those grants encrypted, keyed by user and provider, refreshable, and revocable. The model should never see the raw token.
A practical pattern:
- User completes OAuth for Slack (and separately for GitHub, Google, or helpdesk tools as needed).
- Store
provider,user_id, encrypted refresh material, granted scopes, and expiry. - At tool time, resolve
requester_idto a short-lived access token inside a gateway. - Pass the model an opaque principal identifier such as
slack_user:U123, neverxoxp-.... - Execute the HTTP call in the gateway. Discard the access token from memory after the call.
- On revoke or offboarding, delete or freeze that user's grant without uninstalling the whole app.
freeCodeCamp emphasizes the same split: the agent passes an identifier, and one function turns it into a token at the moment of the call. Practitioner discussions on agent OAuth often describe the same gateway idea so concurrent runs do not multiplex one shared session. Treat community architecture notes as implementation ideas, then verify against Slack's token and OAuth docs for your app type.
Also store which token you intended. Teams that keep only one "Slack token" field will eventually load a bot token into a user-scoped code path. The tell, as freeCodeCamp notes, is that a user token returns only that person's channels; if conversations.history returns a channel the current user was never in, a bot token is in the store.
Identity isolation is more than RBAC on tools. Keep secrets out of prompts, tool schemas, traces, and support logs so a model transcript cannot leak workspace credentials.
Check grants again at tool time
Consent at install time does not replace authorization at run time. Role changes, channel membership changes, and revoked connector grants are common. Before each consequential tool call:
- Resolve the requester from the Slack event or schedule contract.
- Load the grant record for that user and provider.
- Confirm the grant is present, unexpired, and includes the scopes the tool needs.
- Confirm any resource allowlist (channels, repos, knowledge spaces) still matches.
- Deny with a clear error the requester and admins can understand.
- Only then mint or reuse a short-lived access token and execute.
Treat denial as a first-class outcome. If the requester cannot see the private incident channel, the agent should stop, say it lacks access, and avoid summarizing neighboring public content as if it were the requested source. Quietly falling back to the shared bot token recreates the original overreach.
For writes, combine identity checks with your existing approval gates. Rank 84 covers interactive human approval for consequential writes. Per-user permissions answer "whose grants?" Approval answers "should this write proceed now?" High-impact actions usually need both.
Slack's governance guide also asks builders to give admins modular capability controls so teams can start narrow and expand. Map those admin switches onto tool allowlists in your gateway, not onto prompt text that a model can ignore.
Build an audit event that security can read
If every action posts as the bot, Enterprise audit and your own logs still need a requester field you control. Minimum fields for each agent tool event:
timestamprequester_user_idand display name snapshotacting_principal_type(botoruser) and principal idapp_id/agent_idthread_ts/channel_idwhen applicabletool_nameand normalized arguments (redact secrets)scopes_usedandproviderdecision(allowed,denied,approved_write,failed)outcomeand error classmodel/ run id for cost and regression joins
Slack's governance guidance already lists user id, agent id, tools called, model, and outcome-style metrics as common response metrics. Extend that list with acting principal and scopes used so a shared bot notification and a user-scoped write never look identical in storage.
Teams often log model latency and token counts while leaving "who authorized this write" reconstructable only from chat archaeology. Require requester-attributed audit events as a release gate for the agent, not as a later SIEM wish list.
Worked example: support answer with knowledge and ticket write
Scenario: a support lead in #customer-help mentions the agent and asks it to draft an answer from the help center and open a follow-up ticket if the runbook says so.
Shared-bot failure path:
- Bot token reads every channel the app was invited to, including unrelated private triage channels.
- Draft cites a page the lead cannot open in the help center ACL.
- Ticket opens under the integration service account.
- Audit shows the app wrote the ticket. Security cannot map it to the lead without reading Slack history.
Per-user control path:
- Event identifies the support lead as requester.
- Gateway loads the lead's Slack user grant and helpdesk OAuth grant.
- Knowledge read uses the lead's visibility. Missing access returns a denial the lead can escalate.
- Draft cites only opened sources.
- Ticket write uses the lead's helpdesk principal, or pauses for approval if your write policy requires it.
- Audit stores requester, user principal, scopes, sources opened, and ticket id.
The Slack reply can still use a bot token for the app-authored summary if you want the agent branded in-channel, while the privileged reads and external write stay on user grants. Mixed token use is fine. Mixed attribution without a schema is not.
Failure modes to design before rollout
Design for these failures explicitly:
- Missing user grant: requester never completed OAuth. Prompt a connect link; do not fall back to bot token for user-scoped tools.
- Wrong token stored: bot token saved where user token belongs. Detect with a canary read that must fail for bot-only visibility.
- Stale grant after role change: tool-time check fails; notify requester and security contact.
- User deactivated: user-token path breaks by design. That failure mode is correct for interactive work. Scheduled jobs need a named service principal reviewed separately.
- Over-scoped consent: user granted broad scopes the agent does not need. Prefer incremental scope requests and admin policy to disable unused tools.
- Model asks for a disallowed tool: gateway denies; log the attempt; do not "helpfully" retry with the shared bot.
- Cross-user confusion in a shared thread: always key grants by the message author (or an explicit delegate), never by the last bot reply.
NHIMG's identity FAQ stresses ownership and a fast way to cut access when the workflow changes. Your revoke path should disable one user's grants in minutes without uninstalling the app for everyone else.
Pilot checklist for IT and Slack admins
Run a bounded pilot before workspace-wide enablement:
- Inventory every Slack agent tool and label each as bot-token-safe or user-token-required.
- Confirm OAuth requests the correct
user_scopevalues for user-token tools. - Verify encrypted per-user storage and that tokens never appear in model traces.
- Pick two users with different channel and knowledge access. Run the same prompt as each user and confirm different visible sources.
- Revoke one user's grant and confirm only that user's runs fail at
getAccessToken(or equivalent) while the other continues. - Force a denial for a private channel the requester cannot see and confirm no bot-token fallback.
- Open one consequential write and confirm audit shows requester, acting principal, scopes, and outcome.
- Review seven days of denials and writes with security before expanding tools.
A useful pilot result is more than "the agent answered in Slack." Every privileged tool call should show an explainable principal, refuse missing grants, and leave an audit row a human can read without reconstructing the thread.
Underrepresented controls to keep on the permissions board
Select the token type per tool call: use a bot token only for app-identity notifications, and a user token for any read or write that must inherit the requester's channel and resource grants. Store OAuth grants keyed by user and provider behind a gateway so the model receives an opaque principal id, never a raw xoxp- or xoxb- token in prompts, schemas, or logs. Require requester, acting principal, scopes used, and outcome on every agent audit event so security can map a write to a person without reconstructing chat history.
Those three controls change release decisions. Skip per-call token selection and every tool inherits workspace-wide bot privilege. Skip gateway-stored grants and tokens leak into model context while one shared credential serves every teammate. Skip requester-attributed audit fields and security cannot answer who authorized a write after the thread scrolls away.
What to do next
Shared bot tokens remain reasonable for app-identity features. They are a poor default for multi-user AI agents that read private context or write to real systems. Separate requester from acting principal, choose token type per tool call, keep grants behind a gateway, re-check scopes at tool time, and require requester-attributed audit events.
If your team already uses a thread-native agent for Slack work, extend the same permission and citation rules to every connector instead of inventing a weaker shared-bot mode for convenience. Start with one support or knowledge workflow, prove two users see different sources, and prove revoke-one-user works. Those three checks prevent most privilege-collapse incidents before you automate anything customer-visible.
References
- Slack Developer Docs: Tokens documents bot, user, and other token types and when user tokens are required.
- Slack Developer Docs: Governance and trust documents agent audit baselines, who triggered requests, and admin policy controls.
- Slack Developers: Bot and user tokens explained explains
xoxb-versusxoxp-attribution and persistence. - freeCodeCamp: AI agent with per-user OAuth is a practitioner handbook on shared-token failure modes and
authed_user.access_tokenselection. - NHIMG: Slack-connected AI agents as identities argues for treating Slack agents as NHIs with ownership and revocation.
- NHIMG: Govern Slack-native AI workflows covers governance for workflows that can trigger real actions.
- Reddit r/Slack: Shared bot per-user authorship is a practitioner report of shared-bot authorship collapse.
- Kipwise Agent is the product context for thread-native work, requesting-user permissions on applicable actions, citations, and explicit writes.


