- Add the required environment variables to your
.env
file. - Create the utility function to authenticate the user.
app/utils/auth.server.ts
import { json } from "@remix-run/node";
const VALID_USERNAME = process.env.VALID_USERNAME;
const VALID_PASSWORD = process.env.VALID_PASSWORD;
export async function authenticateUser(request: Request) {
const authHeader = request.headers.get("Authorization");
if (!authHeader) {
throw json("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
const [scheme, credentials] = authHeader.split(" ");
if (scheme !== "Basic") {
throw json("Invalid authentication scheme", {
status: 400,
statusText: "Bad Request",
});
}
const [username, password] = Buffer.from(credentials, "base64")
.toString()
.split(":");
if (username !== VALID_USERNAME || password !== VALID_PASSWORD) {
throw json("Invalid credentials", {
status: 401,
statusText: "Unauthorized",
});
}
return true;
}
Call the authenticateUser function in the loader of the protected page.
Handle the happy path, as the errors will be guided to the ErrorBoundary.
app/routes/my_page.tsx
import {
HeadersFunction,
json,
type LoaderFunction,
type MetaFunction,
} from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { authenticateUser } from "~/utils/auth.server";
export const loader: LoaderFunction = async ({ request }) => {
await authenticateUser(request);
return json({ data: "My data" });
};
export const meta: MetaFunction = () => {
return [
{ title: "Protected page example" },
{ name: "description", content: "Protected page example" },
];
};
export default function MyPage() {
const { data } = useLoaderData<typeof loader>();
return <div>{data}</div>;
}
// Move this to `root` if you want to protect all routes
export const headers: HeadersFunction = () => ({
"WWW-Authenticate": "Basic",
});
Nothing fancy in the root, maybe add the Header here for global protection. Also ensure to have a proper error boundary in place.
app/root.tsx
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
export { ErrorBoundary } from "./components/ErrorBoundary";
import "./tailwind.css";
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export function App() {
return <Outlet />;
}
app/components/ErrorBoundary.tsx
import { isRouteErrorResponse, useRouteError } from "@remix-run/react";
const Container = ({ children }: { children: React.ReactNode }) => {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-950 space-y-2">
{children}
</div>
);
};
const Title = ({ children }: { children: React.ReactNode }) => {
return <h1 className="text-4xl text-gray-100">{children}</h1>;
};
const Subtitle = ({ children }: { children: React.ReactNode }) => {
return <h2 className="text-2xl text-gray-400">{children}</h2>;
};
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<Container>
<Title>
{error.status} {error.statusText}
</Title>
<Subtitle>{error.data}</Subtitle>
</Container>
);
} else if (error instanceof Error) {
return (
<Container>
<Title>Error</Title>
<Subtitle>{error.message}</Subtitle>
</Container>
);
} else {
return (
<Container>
<Title>Unknown Error</Title>
<Subtitle>An unknown error occurred</Subtitle>
</Container>
);
}
}