LightningCopilot integrates Microsoft Copilot Studio agents into Salesforce via Lightning Web Components (LWC) with Entra ID authentication, MSAL-based SSO, and Adaptive Cards rendering.
This repository supplies an LWC host shell plus build assets for embedding a Copilot Studio web client inside Salesforce.
This README provides a quick overview.
For deeper, step-by-step guidance, visit my blog at brianbaldock.net.
- Overview
- Features
- Architecture
- Prerequisites
- Quick start
- Configuration
- Renaming the experience
- Authentication flow
- Development workflow
- Building the Copilot bundle
- Deployment
- Troubleshooting
- Security considerations
- Contributing
LightningCopilot provides:
- A Salesforce-native host component (LWC) for Copilot chat.
- Entra ID authentication via MSAL.
- Direct Line channel negotiation.
- Inline error surfacing.
- Adaptive Cards support inside Salesforce UI.
flowchart TD
subgraph Salesforce["Salesforce"]
SR["Static Resources: msalBrowser, copilotStudioClient, adaptiveCards"]
A["LWC: Lightning Copilot"]
MSAL["MSAL SPA: clientId and redirect URIs"]
end
A -- loadScript --> SR
A -- MSAL acquireToken --> MSAL
MSAL -- OIDC and PKCE --> ENTRA["Microsoft Entra ID"]
A -- Agent Config --> COPILOT["Copilot Studio Agent<br>copilotstudio.microsoft.com"]
COPILOT -- Config and Policies --> BAP["Power Platform API: api.bap.microsoft.com"]
COPILOT -- Conversation and token --> DLINE["DirectLine API:<br>directline.botframework.com/token"]
ENTRA -.-> COPILOT
ENTRA:::svc
BAP:::svc
DLINE:::svc
classDef svc fill:#f6f6f6,stroke:#999,color:#333
- Entra ID sign-in and sign-out.
- Event-driven integration (custom DOM events).
- Pluggable styling namespace.
- Build pipeline for Copilot Studio web client assets.
- Error isolation to reduce impact on surrounding Lightning pages.
High-level layers:
- LWC: lightningCopilotAuth
- Helper LWC: inlineError
- Static resource bundle: built Copilot Studio client
- Authentication: MSAL (browser) + Entra ID app registration
- Conversation: Direct Line traffic brokered through the component
Data flow (simplified): User -> LWC sign-in -> MSAL acquires token -> Direct Line starts -> Messages exchanged -> Adaptive Cards rendered.
Data flow (expanded):
sequenceDiagram
autonumber
participant U as User
participant LWC as LightningCopilotAuth (LWC)
participant MSAL as MSAL (Browser)
participant EID as Entra ID
participant DL as Copilot Studio / Direct Line
Note over LWC: Component loads<br/>scripts -> validate embed URL
LWC->>MSAL: initMsal()
MSAL-->>LWC: handleRedirectPromise()
alt Redirect result has account & token
MSAL-->>LWC: AuthenticationResult (accessToken)
LWC->>LWC: onSignedIn()
else No redirect token
LWC->>MSAL: (wire) get Salesforce user email (loginHint)
alt Have email & not yet attempted
LWC->>MSAL: ssoSilent({loginHint, scopes})
MSAL->>EID: /authorize (silent iframe)
EID-->>MSAL: Silent token or error
alt Silent success
MSAL-->>LWC: AuthenticationResult
LWC->>LWC: onSignedIn()
else Silent fails
LWC->>LWC: startAuthFlow()
end
else No email yet
LWC->>LWC: startAuthFlow()
end
LWC->>MSAL: acquireTokenSilent({scopes, account})
MSAL->>EID: /authorize (silent)
alt Silent success
MSAL-->>LWC: AuthenticationResult
LWC->>LWC: onSignedIn()
else Silent fails
LWC->>U: Show "Sign in" button
U->>LWC: Click Sign In
LWC->>MSAL: loginRedirect({scopes, loginHint})
MSAL->>EID: /authorize (interactive + PKCE)
EID-->>MSAL: Redirect with code
MSAL->>EID: /token (exchange + PKCE verifier)
EID-->>MSAL: Access Token
MSAL-->>LWC: AuthenticationResult
LWC->>LWC: onSignedIn()
end
end
Note over LWC: Store accessToken + expiry<br/>schedule silent refresh (~1 min before expiry)
LWC->>DL: createConnection(environmentId, botId, accessToken)
alt Streaming available
DL-->>LWC: Streaming connection (activity$)
LWC->>DL: (postActivity for user messages)
else REST fallback
DL-->>LWC: { dlToken, domain }
LWC->>DL: POST /v3/directline/conversations
DL-->>LWC: conversationId
loop Poll
LWC->>DL: GET /activities?watermark
DL-->>LWC: Activities (messages / typing)
end
LWC->>DL: POST /activities (user messages)
end
loop Before expiry
LWC->>MSAL: acquireTokenSilent({scopes, account})
MSAL->>EID: /authorize (silent)
EID-->>MSAL: New accessToken
MSAL-->>LWC: Updated token (refresh)
Note over LWC: Optionally could renew DL if needed (currently retained)
end
U->>LWC: Logout
alt Top window
LWC->>MSAL: logoutRedirect()
MSAL->>EID: /logout
else In iframe
LWC->>MSAL: logoutPopup()
MSAL->>EID: /logout (popup)
end
LWC->>LWC: clearSession()
Install / prepare:
- Node.js (LTS) and npm.
- Salesforce CLI (sf).
- A Salesforce Org (scratch, sandbox, or production).
- Microsoft Copilot Studio bot with Direct Line channel enabled.
- Entra ID app registration:
- SPA redirect URI(s) matching Salesforce domain(s).
- Expose needed API permissions (if calling downstream resources).
- Optional: logout URL.
# Authenticate to Salesforce
sf org login web --alias MyOrg --instance-url https://login.salesforce.com
sf config set target-org MyOrg --global
# Deploy source
sf project deploy start --target-org MyOrgOpen the org, add the LightningCopilotAuth component to a Lightning page.
Adjust (example placeholders):
- Entra ID client ID: in the component JS (or move to custom metadata / custom settings as needed).
- Direct Line endpoint / secret exchange: implement a server-side function or Named Credential-backed Apex if required.
- Allowed origins: ensure Salesforce domain is in Entra ID app registration redirect URIs.
To publish under a different brand:
- Folder rename:
lightningCopilot/main/default/lwc/lightningCopilotAuth-> new folder name.
- Class / export rename:
- Update exported class
LightningCopilotAuthto new name.
- Update exported class
- Event names:
- Replace:
lightningcopilotauthsignin,lightningcopilotauthsignout,lightningcopilotautherror.
- Replace:
- HTML labels:
- Change “Sign in to Lightning Copilot” to new brand text.
- CSS namespace:
- Root class
.lightning-copilot-shell-> new scoped class.
- Root class
- Logging prefix:
- Update
[LightningCopilotAuth]occurrences.
- Update
- sfdx-project.json:
- Adjust package directory path if source folder renamed.
- README:
- Replace occurrences of LightningCopilot with new brand.
Keep naming internally consistent to avoid broken imports.
- User clicks sign in (if required)
- MSAL initializes with Entra ID app registration.
- Token acquisition (redirect or popup).
- Token passed (or exchanged) for Direct Line conversation token.
- Chat session starts; messages flow.
- Sign-out triggers MSAL logout and emits custom event.
Events (original names):
lightningcopilotauthsigninlightningcopilotauthsignoutlightningcopilotautherror
Typical loop:
-
Modify LWC sources.
-
(Optional) Rebuild Copilot client bundle in
build-src. -
Deploy to target org:
sf project deploy start --target-org MyOrg
-
Test in a Lightning App builder page.
Use scratch orgs for iterative development:
sf org create scratch --definition-file config/project-scratch-def.json --alias DevScratch
sf config set target-org DevScratch --global- Source:
build-src - Output (static resource):
static-resources-buildSteps (example):
# Install dependencies
npm install
# Build (adjust script name if different)
npm run build
# Resulting assets: placed into static-resources-build for deploymentEnsure the static resource is referenced correctly in LWC for loading scripts.
For sandbox / production:
- Validate changes in a scratch org.
- Commit and push.
- Use source deployment or package upload if converting to unlocked package.
- Post-deploy: verify authentication redirect domain availability in Entra ID app registration.
Issue: Sign-in loop
- Check redirect URI mismatch in Entra ID app registration. Issue: Adaptive Card not rendering
- Confirm card JSON version compatibility. Issue: 401 from Direct Line
- Ensure token exchange flow and secret storage handled server-side. Issue: Events not captured
- Verify renamed custom events matched in listeners.
Logging prefix (update if renamed): [LightningCopilotAuth].
- Never commit Direct Line secrets.
- Prefer server-mediated token exchange.
- Limit Entra ID app permissions to minimum scope.
- Consider CSP settings in Salesforce (Lightning Locker / LWC security).
- Review OWASP guidelines for embedding external scripts.
- Fork repository.
- Branch:
feat/<topic>orfix/<issue>. - Include concise commit messages.
- Open pull request with summary and test steps.