Auth0 Single Page App Confusion

So I am creating a Single Page Application using this page to build the app. I was able to create the login page and successfully was able to login my test users and got the correct successful feedback. That is using the index.html sample that was created. I was elated by that! I have the email provider linked to my Auth0 account. Another plus!

Now the troubling part is integrating the Single Page HTML page I created with my Auth0 app. I want the Single Page HTML to be a separate page within my organization’s website. The page is for members only so needs to be secure and only accessible by members.

I tried replacing the “Main Content” on the index.html example with my HTML code. I added the style sheet from my website so the page formatted correctly. But the result is a split screen with my HTML content displayed on the left side of the screen and the Universal Login on the right side. Not what I wanted. No matter whether I am logged in or not the Universal Login always appears.

After doing some research I found this page using newer constructs. Can I use that new construct to develop additional secured pages?

I was unable to use the “import { createAuth0Client } from ‘@auth0/auth0-spa-js’;”. I get “import : The term ‘import’ is not recognized as the name of a cmdlet, function, script file, or operable program." error message.

How to I move forward?

Thanks for all of your help!

Warren

I see Advanced Usage at the bottom of the page I used to create the test app. Maybe just use the popup? I will try it and report back.

I tried the popup script in my app.js and the universal login was popped out. But I still have a split screen with content in several different places on the screen.

I think what I need is a popup for all of the feedback from Auth0 (successful login, Error Messages, Logout success etc.). And I need my secure members HTML page with a logout button as well as a profile area so the member can change his/her information, password etc.

By the way - I figured out that the other SPA page that I mentioned in my first post is actually the JS code in the app.js that I am using. So that confusion is a bit clearer.

Making progress. Just need the next steps.

Thanks again for your help!

Warren

Hi @wsmetz72

Welcome to the Auth0 Community!

The “split screen” and constant login prompt might be happening because your new HTML isn’t wired into the JavaScript state-toggling logic that hides/shows content based on whether the user is logged in. Also, the import error youare receiving appears to be a terminal error; it usually happens when you accidentally paste JavaScript code directly into your computer’s command line (PowerShell) instead of placing it inside a JavaScript file. For this issue, can you confirm that you are adding it as code instead of running it as a command inside the terminal?

Since you are building a Vanilla JS app and likely aren’t using a complex bundler yet, the easiest way to use the new SDK is via a CDN script tag, not an import statement.

In your index.html <head> , ensure you have this script:

<script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js"></script>

Otherwise, in your index.html , wrap all of your secure, members-only HTML inside a single <div> with an ID, and hide it by default using CSS:

<!-- Login UI-->
<div id="login-ui">
  <h2>Welcome to the Org</h2>
  <button id="btn-login">Log In</button>
</div>

<!-- Secure Member Route -->
<div id="members-only-content" style="display: none;">
  <!-- Custom HTML-->
  <h1>Members Only Dashboard</h1>
  <p>Organization Data.</p>
  <button id="btn-logout">Log Out</button>
</div>

Then, in your app.js file, you use the Auth0 client to check the user’s state when the page loads, and update the display accordingly:

let auth0Client = null;

window.onload = async () => {
  auth0Client = await auth0.createAuth0Client({
    domain: "YOUR_AUTH0_DOMAIN",
    clientId: "YOUR_CLIENT_ID",
    authorizationParams: {
      redirect_uri: window.location.origin
    }
  });

  if (location.search.includes("state=") && 
      (location.search.includes("code=") || location.search.includes("error="))) {
    await auth0Client.handleRedirectCallback();
    window.history.replaceState({}, document.title, "/");
  }

  const isAuthenticated = await auth0Client.isAuthenticated();

  if (isAuthenticated) {
    document.getElementById("login-ui").style.display = "none";
    document.getElementById("members-only-content").style.display = "block";
  } else {
    document.getElementById("login-ui").style.display = "block";
    document.getElementById("members-only-content").style.display = "none";
  }
};

document.getElementById("btn-login").addEventListener("click", async () => {
  await auth0Client.loginWithRedirect();
});

document.getElementById("btn-logout").addEventListener("click", () => {
  auth0Client.logout({
    logoutParams: { returnTo: window.location.origin }
  });
});

I would highly recommend checking our SPA JS SDK sample application in regards to building your app in case it helps you understand the concept better.

If you have any other questions, let me know!

Kind Regards,
Nik

1 Like

Hi Nik,

Thank you so much for your quick reply! I will try out your ideas and let you know.

Kind Regards,

Warren

Hi Nick,

So I tried your ideas. The HTML you passed on that blocks the secure content works great! But the app.js code you passed on doesn’t appear to work in my setup. I am working with localhost:5173. Without the “let auth0Client = null;” I get the login, logout and successful login screens provided by the this example if I don’t have “let auth0Client = null;” active. I purposely blocked my email address.

But when I activate that JavaScript that is supposed to block the display of my Members Only data I only get the “Loading Box”.

I did modify the code you provided from this:

domain: “YOUR_AUTH0_DOMAIN”,
clientId: “YOUR_CLIENT_ID”,
authorizationParams: {
redirect_uri: window.location.origin

to this: (So it would work on my localhost:5173.)

domain: import.meta.env.VITE_AUTH0_DOMAIN,
clientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
authorizationParams: {
redirect_uri: window.location.origin

I will note that the index.html file that is included in the “Add Login to Your JavaScript Application” quickstart has this block of code. Strangely enough “Successfully Authenticated is ALWAYS displayed when you successfully login. despite the style="display: none;. Interesting!

 <div id="logged-in" class="logged-in-section" style="display: none;">
      <div class="logged-in-message">✅ Successfully authenticated!</div>
      <h2 class="profile-section-title">Your Profile</h2>
      <div id="profile" class="profile-card"></div>
      <button id="logout-btn" class="button logout">Log Out</button>

I seem to be getting the normal responses from the logs that you would expect if I don’t have “let auth0Client = null;” active.

What am I doing wrong?

Kind Regards,

Warren

I should say that there aren’t any log entries when I do have “let auth0Client = null;” active.

Hi Nik,

Something that might help is I have also tried loading the example SSA, without any updates except my domain and client ID are inserted in the app.js in place of the Vite environment, on my Godaddy webserver. I get the same “Loading” box on the screen that I captured above. My custom domain has been verified by Auth0. I have tried using the tenant domain and the custom domain with the same results

So it seems that there is a hang-up with communication between the localhost or my webserver and the Auth0 system. Do I have something wrong with the syntax? Is there a way of tracing where the problem lies?

Kind Regards,

Warren

Hi Nik,

I took off the solution checkmark for now because we need to resolve communication issues between my localhost 5173 and my Auth0 tenant that is preventing the coding to do its job. Please advise what the next step should be.

Kind Regards,

Warren

Hi again @wsmetz72

Thanks for the further updates, no worries about removing the solution mark, that is encouraged if the proposed solution did not fix the issue.

I believe that the “Loading Box” hang is being caused by a JavaScript crash due to mixing two different code structures since you are using Vite which comes pre-packaged in the Auth0 quickstart download.

You should use the import { createAuth0Client } syntax inside your JavaScript file. Also,when you added let auth0Client = null; alongside the existing quickstart code, it likely caused a “variable already declared” error. When JavaScript crashes, the code that hides the Loading Box never runs, leaving you stuck on that screen.

Since you are using the official Vite quickstart, let’s try to wire your custom Members Only HTML directly into the standard, clean Vite setup without causing conflicts.

Make sure your custom members-only content has a specific ID and is hidden by default:

<!-- The default Quickstart Login UI -->
<div id="login-ui">
  <button id="btn-login">Log In</button>
</div>

<!-- Your Custom Content -->
<div id="members-only-content" style="display: none;">
  <h1>Members Only Dashboard</h1>
  <p>Your secure organization data here.</p>
  <button id="btn-logout">Log Out</button>
</div>

In your main JavaScript file (usually main.js or app.js in the Vite quickstart), you can safely use the import syntax at the very top. Replace the initialization logic with this version:

import { createAuth0Client } from '@auth0/auth0-spa-js';

let auth0Client = null;

window.onload = async () => {
 const auth0Client = await createAuth0Client({
    domain: import.meta.env.VITE_AUTH0_DOMAIN,
    clientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
    authorizationParams: {
      redirect_uri: window.location.origin
    }
  });

  if (location.search.includes("state=") && 
      (location.search.includes("code=") || location.search.includes("error="))) {
    await auth0Client.handleRedirectCallback();
    window.history.replaceState({}, document.title, "/");
  }

  await updateUI();
};

const updateUI = async () => {
  const isAuthenticated = await auth0Client.isAuthenticated();

  const loadingDiv = document.getElementById("loading");
  if (loadingDiv) loadingDiv.style.display = "none";

  if (isAuthenticated) {
    document.getElementById("login-ui").style.display = "none";
    document.getElementById("members-only-content").style.display = "block";
  } else {
    document.getElementById("login-ui").style.display = "block";
    document.getElementById("members-only-content").style.display = "none";
  }
};

document.getElementById("btn-login").addEventListener("click", async () => {
  await auth0Client.loginWithRedirect();
});

document.getElementById("btn-logout").addEventListener("click", () => {
  auth0Client.logout({
    logoutParams: { returnTo: window.location.origin }
  });
});

The communication between your server and Auth0 is likely fine. The hang-up might be happening directly inside your web browser because the browser doesn’t know how to read the raw Vite code you uploaded to GoDaddy.

Also, because you are using a standard GoDaddy webserver, you have two choices on how to fix this:

->If you want to just write HTML and JS files and upload them straight to GoDaddy without dealing with Node.js, Vite, or “building” your app, you must remove the import statements entirely and use the CDN.

<script src="https://cdn.auth0.com/js/auth0-spa-js/2.1/auth0-spa-js.production.js"></script>

In your app.js , remove the import line at the top, and change how you initialize Auth0:**

window.onload = async () => {
 const auth0Client = await auth0.createAuth0Client({
    domain: "YOUR_DOMAIN.auth0.com",
    clientId: "YOUR_CLIENT_ID",
    authorizationParams: {
      redirect_uri: window.location.origin
    }
  });

  // ... the rest of the code to hide the loading box and show the UI
  document.getElementById("loading").style.display = "none";
};

→ If you want to keep using Vite (with the import statements and import.meta.env ), you cannot upload the raw files to GoDaddy.

  1. You must run npm run build in your terminal on your local computer.
  2. Vite will compile all your code into standard, browser-readable files and put them in a new folder named dist .
  3. You then upload only the contents of the dist folder to GoDaddy.

For further troubleshooting, if none of the proposed solutions above work, do you see any errors inside the browser’s network trace during the authentication process which state any kind of error? If the authentication works, you will not see any error within Auth0 logs since it might be an issue with the application itself and not Auth0 related configuration/integration.

Kind Regards,
Nik

1 Like

Hi Nik,

So I have decided to go with GoDaddy only for testing and use CDN. I removed the import command from the app.js file and added the CDN script to the index.html. I still just get the “loading” box.

I removed all of the NPM JSON files that I had loaded on GoDaddy after you pointed out that there might be a conflict. I don’t see any errors in the Networking tab. I can review the content of all of the files I have in my folder. Right now I am just trying to get the example running from the localhost from Quickstart on GoDaddy.

Maybe instead of snippets of code if you could provide a complete app.js code that would work with the Quickstart SPA JavaScript example that would be very helpful. I could just update it with my domain and ClientID. I think the index.html is OK. All I did was add line of script for CDN in the body of the HTML file.

Thanks again for all of your help!

Kind Regards,

Warren

Hi Nik,

I will uninstall Vite and transition to NextJS. I’ll let you know how it goes.

Kind Regards,

Warren

Hi Nik,

I tried the uninstall but got all kinds of errors. So I deleted the JSON files and Module folder and made new ones. Then I built new code just to make sure I was starting off on the right foot instead of reusing code that was reused several times.

Something interesting happened. Instead of clear sailing in localhost for the example QuickStart app I am not able to get all the way through the app.js. The Login works fine. After I login instead of getting Success… I get an OOPS Unauthorized error. I also get a Failed Exchange - Unauthorized in the Auth0 Log page.

I think it is hanging at this line (see below) because I get the same error in Windows Powershell and on the browser. I also get that same error when I try to run my app in GoDaddy. When I open the Inspect troubleshooting window they point to the error message. So something must have been updated in the last month to catch the error. I can dig out the old JSON from my backups but that won’t solve my GoDaddy app.

How should I fix the syntax for the “Catch Err”?

[plugin:vite:import-analysis] Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.
C:/PECBMembers/app.js:28:2
27 | await updateUI();
28 | } catch (err) {
29 | showError(err.message);
| ^
30 | }
31 | }
at TransformPluginContext._formatLog (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:30375:39)
at TransformPluginContext.error (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:30372:14)
at TransformPluginContext.transform (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:27618:10)
at async EnvironmentPluginContainer.transform (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:30164:14)
at async loadAndTransform (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:24512:26)
at async viteTransformMiddleware (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:24306:20)
Click outside, press Esc key, or fix the code to dismiss.
You can also disable this overlay by setting server.hmr.overlay to false in vite.config.js.

THANKS AGAIN for your help!

Kind Regards,

Warren

Hi Nick,

Very interesting indeed! So I let my app run for 9 hours with the Vite code running in the localhost using the Vite app.js and env.local. I sent an email from Auth0 User Management to my user name and now I can login from Locahost:5173!! No errors. All appearing normally on the logs (see below). I can login and log out with no problems!

So I decided to plugin the CDN script in my index.html and remove the import command in app.js and update the Auth0 SDK initialization to remove the Vite construct. When I started the localhost:5173 I discovered that there was a different syntax error reported (see below). I assume this error, and the one I reported earlier, was caused by the JavaScript crashing as you suspected.

[plugin:vite:import-analysis] Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.
C:/PECBMembers/app.js:32:0
33 |  
34 |  // Handle redirect callback
35 |  async function handleRedirectCallback() {
   |  ^
36 |    try {
37 |      await auth0Client.handleRedirectCallback();
    at TransformPluginContext._formatLog (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:30375:39)
    at TransformPluginContext.error (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:30372:14)
    at TransformPluginContext.transform (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:27618:10)
    at async EnvironmentPluginContainer.transform (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:30164:14)
    at async loadAndTransform (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:24512:26)
    at async viteTransformMiddleware (file:///C:/PECBMembers/node_modules/vite/dist/node/chunks/node.js:24306:20)

Click outside, press Esc key, or fix the code to dismiss.
You can also disable this overlay by setting server.hmr.overlay to false in vite.config.js.

So I think there are some other conflicts in the JavaScript. What should I try next?

THANK YOU AGAIN!

Kind Regards,

Warren

Hi again @wsmetz72

Thanks for the updated regarding the matter. I am sorry for my delayed response however I was investigating your finding in order to see how can we proceed further.

I believe a better representation of what you are trying to achieve is this Sample Application in which does provide an accurate app.js file which you can adapt to your vite configuration easily.

// The Auth0 client, initialized in configureClient()
let auth0Client = null;

/**
 * Starts the authentication flow
 */
const login = async (targetUrl) => {
  try {
    console.log("Logging in", targetUrl);

    const options = {
      authorizationParams: {
        redirect_uri: window.location.origin
      }
    };

    if (targetUrl) {
      options.appState = { targetUrl };
    }

    await auth0Client.loginWithRedirect(options);
  } catch (err) {
    console.log("Log in failed", err);
  }
};

/**
 * Executes the logout flow
 */
const logout = async () => {
  try {
    console.log("Logging out");
    await auth0Client.logout({
      logoutParams: {
        returnTo: window.location.origin
      }
    });
  } catch (err) {
    console.log("Log out failed", err);
  }
};

/**
 * Retrieves the auth configuration from the server
 */
const fetchAuthConfig = () => fetch("/auth_config.json");

/**
 * Initializes the Auth0 client
 */
const configureClient = async () => {
  const response = await fetchAuthConfig();
  const config = await response.json();

  auth0Client = await auth0.createAuth0Client({
    domain: config.domain,
    clientId: config.clientId
  });
};

/**
 * Checks to see if the user is authenticated. If so, `fn` is executed. Otherwise, the user
 * is prompted to log in
 * @param {*} fn The function to execute if the user is logged in
 */
const requireAuth = async (fn, targetUrl) => {
  const isAuthenticated = await auth0Client.isAuthenticated();

  if (isAuthenticated) {
    return fn();
  }

  return login(targetUrl);
};

// Will run when page finishes loading
window.onload = async () => {
  await configureClient();

  // If unable to parse the history hash, default to the root URL
  if (!showContentFromUrl(window.location.pathname)) {
    showContentFromUrl("/");
    window.history.replaceState({ url: "/" }, {}, "/");
  }

  const bodyElement = document.getElementsByTagName("body")[0];

  // Listen out for clicks on any hyperlink that navigates to a #/ URL
  bodyElement.addEventListener("click", (e) => {
    if (isRouteLink(e.target)) {
      const url = e.target.getAttribute("href");

      if (showContentFromUrl(url)) {
        e.preventDefault();
        window.history.pushState({ url }, {}, url);
      }
    }
  });

  const isAuthenticated = await auth0Client.isAuthenticated();

  if (isAuthenticated) {
    console.log("> User is authenticated");
    window.history.replaceState({}, document.title, window.location.pathname);
    updateUI();
    return;
  }

  console.log("> User not authenticated");

  const query = window.location.search;
  const shouldParseResult = query.includes("code=") && query.includes("state=");

  if (shouldParseResult) {
    console.log("> Parsing redirect");
    try {
      const result = await auth0Client.handleRedirectCallback();

      if (result.appState && result.appState.targetUrl) {
        showContentFromUrl(result.appState.targetUrl);
      }

      console.log("Logged in!");
    } catch (err) {
      console.log("Error parsing redirect:", err);
    }

    window.history.replaceState({}, document.title, "/");
  }

  updateUI();
};

Also, instead of using loginWithRedirect(), can you try to adapt your code to use loginWithPopup() instead and see if you are able to achieve the redirection to the protected page using a similar updateUI() method?

async function login() {
  try {
    await auth0Client.loginWithPopup();
    await updateUI();
  } catch (err) {
    if (err.error !== 'popup_closed_by_user') {
      showError(err.message);
    }
  }
}

As a suggestion regarding the matter, I would recommend considering using a React and Vite based SPA application since it is easier to implement protected routes. You can check that out here.

Otherwise, can you try removing the script from your code and only use npm install on the spa js library and let me know if there are any changes in the behaviour?

Kind Regards,
Nik

Hi Nik,

No problem! I know you were busy with your investigations.

I believe the GitHub Auth0 example you presented was constructed for Regular Web App (server-side rendering), it uses the Express server, and not Vite Single Page Application (SPA) (client-side rendering).

Right now I just want to get the original Auth0 Vanilla JS running on my GoDaddy server the way it runs on my LocalHost:5173. I think it would just take a couple of tweaks in the app.js JavaScipt.

Things that I know that need to happen to transition from Localhost to GoDaddy

  1. Add the CDN script to the header of my index.html file
  2. Remove the Import statement from the app.js file
  3. Change the initialization commands from Vite to JavaScript that you passed on to me initially.

Are there any other inconsistencies with the JavaScript in the Vanilla JS example that would prohibit it from running in GoDaddy? I did notice one thing that was odd to me. The app.js ends with the command “initAuth0();” Is that meant to start up another app?

So in summary I would like to stay with the Vanilla JS example. I will deal with the onscreen look and feel after I get the original example running online.

Thanks again for your investigations! They are a big help! I think we can nail this!

Kind Regards,

Warren

Hi Nik,

I just wanted to enter a reply to keep this thread going. I know you’re investigating solutions to my questions.

Kind regards,

Warren