Adding Base Auth to Remix

29/10/2023

After playing with this project for a while now, getting some SEO traction and already having some content, I realized it's not ok to have it open for anyone to edit (anyone who finds the links at least). My post and project editing/deleting options were visible for everyone to reach and use. That is, obviously, an extreme security issue which I was aware of, but didn't really tackle by now as the app was really a playground for me more than it was a real asset.

To counter that, I decided I need to add at least some form of protection to it. I don't want to spend too much time on it as it's a side project, I just want it to work and I want to flow to feel natural to me. I have researched https://github.com/sergiodxa/remix-auth and it looks like a decent solution which I might spend time implementing in the future, but it just felt like it's going to take up time from me that I don't really want to spend at the moment. Instead, I wanted something quick and easy that will just work. Base auth, although certainly not the best solution for any complex project, would do the trick here and be much quicker to implement.

To keep things simple, I am keeping my username and password in an env file. I was considering to put it in the database, but I don't plan to have more than one user and don't really want to build a view where to handle it. So in my env, I have added 2 more fields:

ADMINUSER="someusername"
ADMINPW="somepassword"

I have added a file to my utils folder called isAuthorized that has 2 functions, isAuthorized and authHeaders like so:

export const isAuthorized = (request: any) => {
  const header = request.headers.get("Authorization");
  if (!header) return false;
  const base64 = header.replace("Basic ", "");
  const [username, password] = Buffer.from(base64, "base64")
    .toString()
    .split(":");
  return username === process.env.ADMINUSER && password === process.env.ADMINPW;
};

export const authHeaders = () => ({
  "WWW-Authenticate": "Basic",
});

The authHeaders function is something that we need to export from the protected remix routes for the basic auth to pop up, and the isAuthorized function is a simple function that compares the entered values to the ones from our env file.

Next, in our admin routes (ie the routes we want to protect), in the loader function, we add the following:

type LoaderData = { post: Post; authorized: boolean };

export const loader: LoaderFunction = async ({ params, request }) => {
  if (!isAuthorized(request)) {
    return json({ authorized: false }, { status: 401 });
  }
  ...

This will return a status 401 page and will also return a property with the authorized:false value to our component, which we can then use to return an unauthorized message. In my implementation, I did it like this:

const addEditPost = () => {
  const { post, authorized } = useLoaderData();

  if (!authorized) {
    return <UnauthorizedMessage />;
  }
 /* Rest of the component... */
 

The UnauthorizedMessage component is a simple component that I am reusing everywhere in the app when somebody tries to close the basic auth. This is how it looks for me, with a little bit of animation to fit the rest of the website:

import { motion } from "framer-motion";
import { animationDelay, fadeInAnimation } from "../utils/AnimationVariants";

export const UnauthorizedMessage = () => {
  return (
    <motion.h1
      {...fadeInAnimation}
      transition={{ delay: animationDelay[2] }}
      className="text-center text-white text-2xl mt-4"
    >
      Unauthorized
    </motion.h1>
  );
};

And there you go! With this, you should be able to add your own simple basic auth authentication to your remix site. In case you want to check the code (which I might update in the future), you can always check the github repo for this website which uses the same system as outlined by clicking here. Thank you for reading, I hope this helped someone!

Share this post :