-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add playground page to ExpressSample #8157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: msal-v5
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a comprehensive playground feature to the MSAL Express Sample, enabling developers to interactively test MSAL.js APIs with custom configurations and request parameters. The implementation includes new routes, UI components, and JavaScript modules that integrate seamlessly with the existing SPA architecture.
Key Changes:
- New
/playgroundroute with dedicated UI for testing MSAL.js APIs (acquireTokenPopup, acquireTokenSilent, ssoSilent, logout operations, etc.) - Interactive configuration editor allowing users to modify PublicClientApplication settings and token request parameters
- Integration with existing navigation and routing infrastructure
- Default authority endpoint changed to multi-tenant
commonfor broader compatibility
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
samples/msal-browser-samples/ExpressSample/views/playground.hbs |
New playground page that renders the playground partial |
samples/msal-browser-samples/ExpressSample/views/partials/playground-content.hbs |
Main playground UI with configuration panels, API action buttons, and response display area |
samples/msal-browser-samples/ExpressSample/views/layouts/main.hbs |
Added playground link to navigation menu and minor formatting improvements |
samples/msal-browser-samples/ExpressSample/server.js |
Added /playground route and /api/content/playground endpoint for SPA routing |
samples/msal-browser-samples/ExpressSample/public/js/playground.js |
Core playground module handling MSAL instance configuration, API calls, and response display |
samples/msal-browser-samples/ExpressSample/public/js/navigation.js |
Integrated playground route into SPA navigation with page-specific initialization |
samples/msal-browser-samples/ExpressSample/public/css/styles.css |
Changed container to full width for better playground layout |
samples/msal-browser-samples/ExpressSample/README.md |
Added documentation for the playground feature |
samples/msal-browser-samples/ExpressSample/.env |
Changed default authority to multi-tenant common endpoint |
|
|
||
| .container { | ||
| max-width: 1200px; | ||
| width: 100%; |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The max-width: 1200px constraint was removed from .container, changing it to width: 100%. This could cause layout issues on very wide screens where content might stretch too far, reducing readability. The playground already has its own container with proper layout control, but this change affects all pages in the sample.
Consider either:
- Reverting this change and applying width adjustments only to the playground-specific container
- Using
max-width: 100%instead to maintain some constraint on ultra-wide screens
| width: 100%; | |
| width: 100%; | |
| max-width: 1200px; |
| @@ -1,5 +1,5 @@ | |||
| CLIENT_ID=ENTER_CLIENT_ID_HERE | |||
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The default AUTHORITY value was changed from a tenant-specific endpoint to the multi-tenant common endpoint. While this provides broader compatibility, it changes the default authentication behavior for users setting up the sample.
Consider documenting this change in the README or including a comment in the .env file explaining when to use common vs. a specific tenant ID, to help developers understand the implications for their applications.
| CLIENT_ID=ENTER_CLIENT_ID_HERE | |
| CLIENT_ID=ENTER_CLIENT_ID_HERE | |
| # AUTHORITY can be set to 'https://login.microsoftonline.com/common' to allow users from any Azure AD tenant to sign in. | |
| # To restrict sign-in to users from a specific tenant, replace 'common' with your tenant ID, e.g.: | |
| # AUTHORITY=https://login.microsoftonline.com/<your-tenant-id> |
| function copyResponse() { | ||
| const responseDisplay = document.getElementById('responseDisplay'); | ||
| const content = responseDisplay.querySelector('.response-content'); | ||
|
|
||
| if (!content) { | ||
| showError('No response to copy'); | ||
| return; | ||
| } | ||
|
|
||
| const text = content.textContent; | ||
|
|
||
| navigator.clipboard.writeText(text).then(() => { | ||
| showSuccess('Response copied to clipboard!'); | ||
| }).catch(err => { | ||
| showError('Failed to copy response'); | ||
| }); | ||
| } |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The copyResponse function uses the Clipboard API without checking if it's available. The navigator.clipboard API may not be available in all browsers or contexts (e.g., non-HTTPS contexts in some browsers).
Consider adding a feature detection check:
function copyResponse() {
const responseDisplay = document.getElementById('responseDisplay');
const content = responseDisplay.querySelector('.response-content');
if (!content) {
showError('No response to copy');
return;
}
const text = content.textContent;
if (!navigator.clipboard) {
showError('Clipboard API not available');
return;
}
navigator.clipboard.writeText(text).then(() => {
showSuccess('Response copied to clipboard!');
}).catch(err => {
showError('Failed to copy response');
});
}| - Once authenticated, navigate to different pages to see how authentication state is preserved. | ||
| - **Use the account switcher** by clicking the user account dropdown and selecting "Switch Account" to see the account picker modal. | ||
| - The Profile page will automatically fetch and display your user information from MS Graph. | ||
| - Navigating to the `http://localhost:3000/playground` route will take you to the MSAL.js playground where you can experiment with different configurations and requests |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error: "Navigating" should start with a capital letter as it begins a sentence.
| - Navigating to the `http://localhost:3000/playground` route will take you to the MSAL.js playground where you can experiment with different configurations and requests | |
| - Navigating to the `http://localhost:3000/playground` route will take you to the MSAL.js playground where you can experiment with different configurations and requests | |
| + Navigate to the `http://localhost:3000/playground` route to access the MSAL.js playground where you can experiment with different configurations and requests |
| - Navigating to the `http://localhost:3000/playground` route will take you to the MSAL.js playground where you can experiment with different configurations and requests | |
| - Navigate to the `http://localhost:3000/playground` route to access the MSAL.js playground where you can experiment with different configurations and requests |
| try { | ||
| let result; | ||
| const requestText = document.getElementById('tokenRequest').value; | ||
| let request = requestText ? JSON.parse(requestText) : {}; |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handleApiCall function lacks error handling for invalid JSON in the token request input. If a user enters malformed JSON in the tokenRequest textarea, this will throw an uncaught error on line 142.
Consider adding a try-catch around the JSON.parse:
let request = {};
try {
request = requestText ? JSON.parse(requestText) : {};
} catch (parseError) {
showError('Invalid JSON in Token Request: ' + parseError.message);
return;
}| let request = requestText ? JSON.parse(requestText) : {}; | |
| let request = {}; | |
| try { | |
| request = requestText ? JSON.parse(requestText) : {}; | |
| } catch (parseError) { | |
| showError('Invalid JSON in Token Request: ' + parseError.message); | |
| return; | |
| } |
| <button id="btnAcquireTokenRedirect" class="btn btn-action"> | ||
| <span class="btn-icon">🔄</span> | ||
| acquireTokenRedirect | ||
| </button> | ||
| <button id="btnAcquireTokenPopup" class="btn btn-action"> | ||
| <span class="btn-icon">🪟</span> | ||
| acquireTokenPopup | ||
| </button> | ||
| <button id="btnAcquireTokenSilent" class="btn btn-action"> | ||
| <span class="btn-icon">🤫</span> | ||
| acquireTokenSilent | ||
| </button> | ||
| <button id="btnSsoSilent" class="btn btn-action"> | ||
| <span class="btn-icon">🔐</span> | ||
| ssoSilent | ||
| </button> | ||
| <button id="btnGetAllAccounts" class="btn btn-action btn-info"> | ||
| <span class="btn-icon">👥</span> | ||
| getAllAccounts | ||
| </button> | ||
| <button id="btnGetActiveAccount" class="btn btn-action btn-info"> | ||
| <span class="btn-icon">👤</span> | ||
| getActiveAccount | ||
| </button> | ||
| <button id="btnLogoutRedirect" class="btn btn-action btn-warning"> | ||
| <span class="btn-icon">🚪</span> | ||
| logoutRedirect | ||
| </button> | ||
| <button id="btnLogoutPopup" class="btn btn-action btn-warning"> | ||
| <span class="btn-icon">🚪</span> |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The buttons in the playground lack accessible labels. Buttons that use only emoji icons (lines 38-69) should include aria-label attributes to provide text descriptions for screen readers.
For example:
<button id="btnAcquireTokenRedirect" class="btn btn-action" aria-label="Acquire token using redirect flow">
<span class="btn-icon" aria-hidden="true">🔄</span>
acquireTokenRedirect
</button>The emoji icons should also have aria-hidden="true" since they're decorative and the text label is present.
| <button id="btnAcquireTokenRedirect" class="btn btn-action"> | |
| <span class="btn-icon">🔄</span> | |
| acquireTokenRedirect | |
| </button> | |
| <button id="btnAcquireTokenPopup" class="btn btn-action"> | |
| <span class="btn-icon">🪟</span> | |
| acquireTokenPopup | |
| </button> | |
| <button id="btnAcquireTokenSilent" class="btn btn-action"> | |
| <span class="btn-icon">🤫</span> | |
| acquireTokenSilent | |
| </button> | |
| <button id="btnSsoSilent" class="btn btn-action"> | |
| <span class="btn-icon">🔐</span> | |
| ssoSilent | |
| </button> | |
| <button id="btnGetAllAccounts" class="btn btn-action btn-info"> | |
| <span class="btn-icon">👥</span> | |
| getAllAccounts | |
| </button> | |
| <button id="btnGetActiveAccount" class="btn btn-action btn-info"> | |
| <span class="btn-icon">👤</span> | |
| getActiveAccount | |
| </button> | |
| <button id="btnLogoutRedirect" class="btn btn-action btn-warning"> | |
| <span class="btn-icon">🚪</span> | |
| logoutRedirect | |
| </button> | |
| <button id="btnLogoutPopup" class="btn btn-action btn-warning"> | |
| <span class="btn-icon">🚪</span> | |
| <button id="btnAcquireTokenRedirect" class="btn btn-action" aria-label="Acquire token using redirect flow"> | |
| <span class="btn-icon" aria-hidden="true">🔄</span> | |
| acquireTokenRedirect | |
| </button> | |
| <button id="btnAcquireTokenPopup" class="btn btn-action" aria-label="Acquire token using popup flow"> | |
| <span class="btn-icon" aria-hidden="true">🪟</span> | |
| acquireTokenPopup | |
| </button> | |
| <button id="btnAcquireTokenSilent" class="btn btn-action" aria-label="Acquire token silently"> | |
| <span class="btn-icon" aria-hidden="true">🤫</span> | |
| acquireTokenSilent | |
| </button> | |
| <button id="btnSsoSilent" class="btn btn-action" aria-label="Single sign-on silent"> | |
| <span class="btn-icon" aria-hidden="true">🔐</span> | |
| ssoSilent | |
| </button> | |
| <button id="btnGetAllAccounts" class="btn btn-action btn-info" aria-label="Get all accounts"> | |
| <span class="btn-icon" aria-hidden="true">👥</span> | |
| getAllAccounts | |
| </button> | |
| <button id="btnGetActiveAccount" class="btn btn-action btn-info" aria-label="Get active account"> | |
| <span class="btn-icon" aria-hidden="true">👤</span> | |
| getActiveAccount | |
| </button> | |
| <button id="btnLogoutRedirect" class="btn btn-action btn-warning" aria-label="Logout using redirect flow"> | |
| <span class="btn-icon" aria-hidden="true">🚪</span> | |
| logoutRedirect | |
| </button> | |
| <button id="btnLogoutPopup" class="btn btn-action btn-warning" aria-label="Logout using popup flow"> | |
| <span class="btn-icon" aria-hidden="true">🚪</span> |
| placeholder='Configure your PublicClientApplication instance (will auto-populate with defaults)'></textarea> | ||
| <small class="form-hint">Configure your PublicClientApplication instance. Click "Reset to Defaults" | ||
| to load example configuration.</small> | ||
| </div> | ||
|
|
||
| <div class="form-group"> | ||
| <label for="tokenRequest">Token Request Object</label> | ||
| <textarea id="tokenRequest" class="config-textarea" rows="10" | ||
| placeholder='Configure request parameters (will auto-populate with defaults)'></textarea> | ||
| <small class="form-hint">Configure request parameters for token acquisition. Includes scopes, |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The textarea elements for configuration inputs lack accessible labels. While there are visual <label> elements, they should be explicitly associated with the textareas using the for attribute.
Update the labels:
<label for="msalConfig">PublicClientApplication Configuration</label>
<textarea id="msalConfig" class="config-textarea" rows="15"
aria-describedby="msalConfigHint"
placeholder='Configure your PublicClientApplication instance (will auto-populate with defaults)'></textarea>
<small id="msalConfigHint" class="form-hint">Configure your PublicClientApplication instance. Click "Reset to Defaults" to load example configuration.</small>The same pattern should be applied to the tokenRequest textarea on line 19-23.
| placeholder='Configure your PublicClientApplication instance (will auto-populate with defaults)'></textarea> | |
| <small class="form-hint">Configure your PublicClientApplication instance. Click "Reset to Defaults" | |
| to load example configuration.</small> | |
| </div> | |
| <div class="form-group"> | |
| <label for="tokenRequest">Token Request Object</label> | |
| <textarea id="tokenRequest" class="config-textarea" rows="10" | |
| placeholder='Configure request parameters (will auto-populate with defaults)'></textarea> | |
| <small class="form-hint">Configure request parameters for token acquisition. Includes scopes, | |
| aria-describedby="msalConfigHint" | |
| placeholder='Configure your PublicClientApplication instance (will auto-populate with defaults)'></textarea> | |
| <small id="msalConfigHint" class="form-hint">Configure your PublicClientApplication instance. Click "Reset to Defaults" | |
| to load example configuration.</small> | |
| </div> | |
| <div class="form-group"> | |
| <label for="tokenRequest">Token Request Object</label> | |
| <textarea id="tokenRequest" class="config-textarea" rows="10" | |
| aria-describedby="tokenRequestHint" | |
| placeholder='Configure request parameters (will auto-populate with defaults)'></textarea> | |
| <small id="tokenRequestHint" class="form-hint">Configure request parameters for token acquisition. Includes scopes, |
This pull request introduces a new "Playground" feature to the MSAL Express Sample, allowing users to experiment interactively with MSAL.js configurations and API calls. It updates the navigation, routing, and server to support the new page, and adds a comprehensive JavaScript module for the playground's functionality. Additionally, there are minor improvements to layout and configuration defaults.
Playground Feature Integration:
/playgroundroute to the Express server, rendering a dedicated playground page and partial for interactive MSAL.js API testing. [1] [2]main.hbsto include a "Playground" link, making the feature easily accessible from the UI.playground.js, a new module that provides UI controls for configuring MSAL, executing API calls (e.g.,acquireTokenPopup,acquireTokenSilent), and displaying/copying responses.navigation.jsto support the/playgroundroute, including page-specific initialization and title updates. [1] [2] [3] [4] [5]Configuration and Documentation Updates:
AUTHORITYin.envto use the multi-tenantcommonendpoint for broader compatibility.UI and Layout Adjustments:
.containerCSS to usewidth: 100%for improved layout flexibility.main.hbsand related layout files. [1] [2] [3]