I'm integrating a Microsoft Copilot Studio agent into my company's custom groupware web app (C#), and I want to avoid showing any Microsoft login popups or authentication prompts inside the chat.
My Goal
Authenticate manually
) in Copilot Studio.What I’ve Tried
Followed the official Microsoft docs:
https://learn.microsoft.com/en-us/microsoft-copilot-studio/configure-sso?tabs=webApp
Sample code from GitHub:
https://github.com/microsoft/CopilotStudioSamples/tree/main/SSOSamples/SSOwithEntraID
YouTube guide:
https://www.youtube.com/watch?v=gvrYegvyzIk
Implemented MSAL and configured acquireTokenSilent()
with the right scopes.
It works only after the user has manually signed in using MSAL popup or redirect.
I also tried intercepting the tokenExchange
process and manually injecting a token acquired via the App-only client_credentials flow (Azure AD app with Graph API permissions).
Even then, Copilot shows the OAuth card again or fails with 502 Bad Gateway errors.
Problem
tokenExchange
, Copilot rejects it.My Questions
tokenExchange
?Any help or recommendations are greatly appreciated. 🙏
Let me know if you need code snippets from my exchangeTokenAsync()
function or Web Chat configuration.
Note: To fully bypass login prompts in Microsoft Copilot Studio using manual authentication, you must pre-authenticate users in your app and securely pass a valid user-delegated access token (not an app-only token) to the token exchange endpoint.
Microsoft Copilot Studio bots require user-delegated access tokens to provide user-specific experiences (contextual history, personalization, etc.). These tokens cannot be obtained silently unless the user has:
Even if you have the user’s UPN, Object ID, or session in your system, Microsoft’s identity platform requires a login consent from the user to issue a token on their behalf.
Note: Tokens from the
client_credentials
(app-only) flow will be rejected by Copilot’stokenExchange
because they don’t represent a user identity.
Sample : Send Token to Bot via tokenExchange
Event:
const token = await getToken();
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token: '<Your Direct Line Token>' }),
userID: '<user-id>',
store: window.WebChat.createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'tokenExchange',
value: { token }
}
});
}
return next(action);
})
}, document.getElementById('webchat'));
acquireTokenSilent()
to avoid repeated prompt.