heroBy me

How to setup Single Page Applications with Auth0 in local environment without CORS issues

I used Auth0 in a Single Page Application (SPA) side project recently, because I didn't want to spend the time to build all the auth related features e.g. password reset, sign up, etc. Surprisingly it was extremely painful to get it working locally due to HTTPS, CORS and localhost issues. In this blog post, I'll be walking my final setup with all of that working locally.

You might also want to check out How to integrate Auth0 authenticated Frontend applications with API services after are done setting up the Frontend app.

Simple app architecture

I am going for simple and very standard app architecture in my project. I wanted to separate my frontend from the backend because I didn't want Server Sider Rendering (SSR). When the user visits my web app, I want the frontend to call the backend for any data.

In this case, I'm using React (create-react-app) on the frontend and Node express server on the backend.

Simple SPA architecture diagram

Simple app with Auth0

Here's an updated diagram with Auth0 added to the picture. Now the flow is a little more complex. When someone visits my web app, I now want to redirect them to Auth0 for sign up or sign in before returning to my app to continue with the user journey (steps 1, 2 and 3 in the diagram).

When the user wants to edit any app data, the frontend will attach the bearer token from Auth0 and send it with the request to the backend (steps 4 - 5). The backend will then verify the user is who they claim they are by calling Auth0 with that bearer token, then return the data to the frontend if all is good (steps 6 - 8). Finally, the front end will display the updated UI on the browser (step 9).

SPA with Auth0 architecture diagram

This flow is more complex compared to what we started with, but it is still pretty standard. Any sort of auth service or Single Sign On (SSO) will involve a similar process. Now on to the more complex part - local setup.

Auth0 in the local environment

While Auth0 has dedicated a Test Applications Locally section in their website that focuses on local app testing, unfortunately, it is pretty basic as far as documentation goes. It is tough to pinpoint which type of app architecture they are referring to. As far as I understand this section seems to be for SSR apps, not SPA. There's also a section on HTTPS in Development (I'll explain why we need HTTPS later), which I'm certain isn't for the architecture in our diagram above.

So long story short, I didn't find any good guidance on Auth0's website so I had to forge my own.

We need HTTPS everywhere

We cannot authenticate with Auth0 if our frontend is running in HTTP mode. If you do, then you'll get the following error message.

Error: auth0-spa-js must run on a secure origin.
See https://github.com/auth0/auth0-spa-js/blob/master/FAQ.md#why-do-i-get-auth0-spa-js-must-run-on-a-secure-origin for more information.

Luckily, it is pretty easy to get around this if you are using create react app, it is as simple as HTTPS=true to your start command e.g. HTTPS=true npm start. See more about it here.

Doing this fixes the issue with Auth0, but now Chrome will complain that our HTTPS frontend is now sending non-secure requests (HTTP) to the backend. So next we will need to run the backend in HTTPS mode. To do this with Express, we will need to generate some certificates, I recommend using mkcert. This will output two files a key and a certificate, which we will both need to run the Express server in HTTPS mode. You will then do something like the following.

import fs from "fs";
import https from "https";

...

const privateKey = fs.readFileSync("../../key.pem", "utf8");
const certificate = fs.readFileSync("../../cert.pem", "utf8");
const credentials = { key: privateKey, cert: certificate };

...

const httpsServer = https.createServer(credentials, app);

httpsServer.listen(process.env.PORT || "8000");

Well, now we have these certificates we might as well use them on the frontend. Create react app to the rescue again! All we need to do is include the following in our .env file and react-scripts will pick it up during development mode automatically.

SSL_CRT_FILE=../../cert.pem
SSL_KEY_FILE=../../key.pem

We can't be on localhost

To do step 5 in the second diagram, we need to use getAccessTokenSilently() (again I'm using React, your mileage may vary) to extract the bearer token we need to pass to our backend. This, however, will trigger a consent check (even though this is a first-party application!) and if we are running on [localhost](http://localhost) it will fail silently. You can read more about it on the User Consent and Third-Party Applications page.

If you are on mac or linux then edit /etc/host, if you are windows it is C:\Windows\System32\Drivers\etc\hosts. You could replace [localhost](http://localhost) with [example.site](http://example.site) but I prefer to keep both since I work on codebases that assume localhost as well.

127.0.0.1  localhost example.site

CORS, CORS, CORS

CORS on the local environment is so frustrating. If you follow the instructions above, then hopefully in your Express server you can just add the following and everything should work.

import cors from "cors";

...

app.use(cors());

Alternatively, consider adding some whitelist logic.

import cors from "cors";

...

const whitelist = process.env.ORIGIN_WHITELIST_URL || "https://production.site";
const corsOptions = {
  origin: function (origin: any, callback: any) {
    if (whitelist.includes(origin) || !origin) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
};

...

app.use(cors(corsOptions));

If it still doesn't work, try allowing any origin. Of course, this isn't secure, so come back to this later and bulletproof it for production.

import cors from "cors";

...

const corsOptions = {
  origin: '*',
};

...

app.use(cors(corsOptions));

...

Alternative service to auth0

If you are thinking of using Auth0, I came across another service called Magic which seems to offer a much better dev experience as a whole. Their local environment setup seems too good to be true, check it out here. However, the reason I went with Auth0 at the end is because of their generous free tier which allows up to 1000 active users. Magic in comparison charges based on the number of user logins and there is no free allowance.

Buy Me A Coffee