Created
December 17, 2025 00:04
-
-
Save nicosabena/d1965eebccc2669eaf0ae2aadcb376e5 to your computer and use it in GitHub Desktop.
How to request an MFA API from the browser using auth0-spa-js v2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <script src="https://cdn.auth0.com/js/auth0-spa-js/2.11/auth0-spa-js.production.js"></script> | |
| <title>MFA API Test</title> | |
| <style> | |
| .hidden { | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <a id="homeurl" href="#">Home</a> | |
| <h2>MFA API Test</h2> | |
| <div id="loading">Initializing...</div> | |
| <div id="buttons" class="hidden"> | |
| <h3>Log in</h3> | |
| <div><button type="button" onclick="initialLogin()">Login</button></div> | |
| <h3>Get MFA API token</h3> | |
| <div><button type="button" onclick="getMFAToken()">Get MFA API token</button></div> | |
| </div> | |
| <hr> | |
| <div id="logs"></div> | |
| <script> | |
| // Configuration | |
| const auth0Config = { | |
| tenantDomain: "YOUR_AUTH0_TENANT_DOMAIN", // e.g. acme.us.auth0.com | |
| clientId: "YOUR_CLIENT_ID", // YOUR_CLIENT_ID | |
| audience: "API_AUDIECE" // Your backend API audience (not the MFA API). Omit if not using one. | |
| }; | |
| const thisPageUrl = "http://localhost:3000/"; | |
| // Get logs container | |
| const logsContainer = document.querySelector('#logs'); | |
| // Logging utilities | |
| const write = (content, isCode) => { | |
| if (content === '---') { | |
| logsContainer.innerHTML = `${logsContainer.innerHTML}<hr>`; | |
| return; | |
| } | |
| const tag = isCode ? 'code' : 'p'; | |
| logsContainer.innerHTML = `${logsContainer.innerHTML}<${tag}>${content}</${tag}>`; | |
| if (isCode) { | |
| logsContainer.innerHTML += '<br>'; | |
| } | |
| }; | |
| const writeCollapsible = (summary, details) => { | |
| const summaryHtml = `<summary>${summary}</summary>`; | |
| logsContainer.innerHTML = `${logsContainer.innerHTML}<details>${summaryHtml}<pre>${details}</pre></details>`; | |
| }; | |
| let auth0Client; | |
| const init = async () => { | |
| auth0Client = new auth0.Auth0Client({ | |
| domain: auth0Config.tenantDomain, | |
| clientId: auth0Config.clientId, | |
| authorizationParams: { | |
| redirect_uri: thisPageUrl, | |
| scope: "openid profile email", | |
| audience: "Test" | |
| } | |
| }); | |
| document.getElementById('loading').classList.add('hidden'); | |
| document.getElementById('buttons').classList.remove('hidden'); | |
| }; | |
| const writeAuthDetails = async (token, operation) => { | |
| const authenticated = await auth0Client.isAuthenticated(); | |
| if (!token) { | |
| write(`<strong>${operation} failed</strong>`); | |
| return; | |
| } | |
| write(`<strong>${operation} successful</strong>`); | |
| write('Access token:'); | |
| write(token, true); | |
| // Decode and display JWT payload if it's a JWT token | |
| if (token.startsWith('eyJ')) { | |
| const accessTokenPayload = JSON.parse(atob(token.split('.')[1])); | |
| write('Access token payload:'); | |
| writeCollapsible(JSON.stringify(accessTokenPayload), JSON.stringify(accessTokenPayload, null, 4)); | |
| } | |
| }; | |
| const writeError = (error) => { | |
| window.err = error; // For debugging | |
| write('<strong>Error received</strong>'); | |
| write(`Error: ${error.error}\n`, true); | |
| write(`Message: ${error.error_description}`, true); | |
| }; | |
| // Perform the initial login and get a token for the regular API | |
| window.initialLogin = async () => { | |
| try { | |
| await auth0Client.loginWithPopup(); | |
| const token = await auth0Client.getTokenSilently(); | |
| writeAuthDetails(token, "Login"); | |
| } catch (err) { | |
| writeError(err); | |
| } | |
| }; | |
| window.getMFAToken = async () => { | |
| try { | |
| const mfaAuthorizationParams = { | |
| audience: `https://${auth0Config.tenantDomain}/mfa/`, | |
| scope: 'read:authenticators' | |
| }; | |
| // Present a popup first in case the user needs to give consent, perform an MFA challenge, | |
| // or do any other interactive action. | |
| // loginWithPopup (and loginWithRedirect) will store the authorization results in the cache. | |
| // Another option would be to try getTokenSilently() and do the popup if it fails. | |
| await auth0Client.loginWithPopup({ authorizationParams: mfaAuthorizationParams }); | |
| // Now that the interaction is finished, we can retrieve the tokens. | |
| // At this point, getTokenSilently() will be retrieving the tokens from the cache. | |
| // It is important that the authorizationParams here include the same audience (the MFA API identifier) and scope. | |
| const token = await auth0Client.getTokenSilently({ authorizationParams: mfaAuthorizationParams }); | |
| writeAuthDetails(token, "MFA Token"); | |
| } catch (error) { | |
| writeError(error); | |
| return; | |
| } | |
| }; | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment