Skip to content

Conversation

@tnorling
Copy link
Collaborator

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:

  • Added a new /playground route to the Express server, rendering a dedicated playground page and partial for interactive MSAL.js API testing. [1] [2]
  • Updated the main navigation in main.hbs to include a "Playground" link, making the feature easily accessible from the UI.
  • Created playground.js, a new module that provides UI controls for configuring MSAL, executing API calls (e.g., acquireTokenPopup, acquireTokenSilent), and displaying/copying responses.
  • Enhanced SPA routing and page logic in navigation.js to support the /playground route, including page-specific initialization and title updates. [1] [2] [3] [4] [5]

Configuration and Documentation Updates:

  • Changed the default AUTHORITY in .env to use the multi-tenant common endpoint for broader compatibility.
  • Updated the README to document the new Playground feature and its route.

UI and Layout Adjustments:

  • Adjusted .container CSS to use width: 100% for improved layout flexibility.
  • Minor formatting and accessibility improvements in main.hbs and related layout files. [1] [2] [3]

@tnorling tnorling requested a review from a team as a code owner November 21, 2025 21:49
Copilot AI review requested due to automatic review settings November 21, 2025 21:49
Copilot finished reviewing on behalf of tnorling November 21, 2025 21:52
Copy link
Contributor

Copilot AI left a 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 /playground route 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 common for 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%;
Copy link

Copilot AI Nov 21, 2025

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:

  1. Reverting this change and applying width adjustments only to the playground-specific container
  2. Using max-width: 100% instead to maintain some constraint on ultra-wide screens
Suggested change
width: 100%;
width: 100%;
max-width: 1200px;

Copilot uses AI. Check for mistakes.
@@ -1,5 +1,5 @@
CLIENT_ID=ENTER_CLIENT_ID_HERE
Copy link

Copilot AI Nov 21, 2025

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.

Suggested change
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>

Copilot uses AI. Check for mistakes.
Comment on lines +287 to +303
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');
});
}
Copy link

Copilot AI Nov 21, 2025

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');
    });
}

Copilot uses AI. Check for mistakes.
- 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
Copy link

Copilot AI Nov 21, 2025

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.

Suggested change
- 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
Suggested change
- 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

Copilot uses AI. Check for mistakes.
try {
let result;
const requestText = document.getElementById('tokenRequest').value;
let request = requestText ? JSON.parse(requestText) : {};
Copy link

Copilot AI Nov 21, 2025

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;
}
Suggested change
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;
}

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +67
<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>
Copy link

Copilot AI Nov 21, 2025

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.

Suggested change
<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>

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +22
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,
Copy link

Copilot AI Nov 21, 2025

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.

Suggested change
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,

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants