Musings of a Programmer

Rarely-used blog of Dan Harper.
View all blog posts

I recently needed to add a temporary section to a website which should only be visible to me. This site didn't have any other auth configured, and it wouldn't need it after this, so I was hesitant to pull in a full auth system.

Instead, I opted for basic.. Basic Auth :) This is that old-school login where the browser prompts for a username/password in a native dialog and persists it for your session.

With Next.js, it was simple enough - just one manual route.ts file to bounce between.

Protect an endpoint with a function which'll throw a redirect to a login page:

// src/app/some/directory/page.tsx
import {ensureAuthorized} from '@/server/auth';

export default function SomePage() {
  ensureAuthorized(); // <-- throw if not logged in, redirect to /auth

  // ...
}

Implementing that "login" page, we check the base64'd username/password that may have been submitted. If it's incorrect, or hasn't been provided yet, we return a WWW-Authenticate response which'll prompt again for a username/password:

// src/auth/route.ts
import {checkBasicAuthHeader} from '@/server/auth';
import {NextResponse} from 'next/server';
import {redirect} from 'next/navigation';

export async function GET(request: Request) {
  const authHeader = request.headers.get('authorization');

  if (!checkBasicAuthHeader(authHeader)) {
    return new NextResponse('please login...', {
      status: 401,
      headers: {
        'WWW-Authenticate': 'Basic realm="protected"',
      },
    });
  }

  return redirect('/new');
}

And a simple implementation of the helper methods - checkBasicAuthHeader verifies if the header string contains the correct username & password.

// src/server/auth.ts
import {headers} from 'next/headers';
import {redirect} from 'next/navigation';

const THE_USERNAME = 'abc';
const THE_PASSWORD = '123'; // you could put these in env, or you can yolo it

export function ensureAuthorized() {
  if (!isAuthorized()) {
    redirect('/auth');
  }
}

export function isAuthorized() {
  const authHeader = headers().get('authorization');
  return checkBasicAuthHeader(authHeader);
}

export function checkBasicAuthHeader(authHeader: string | null) {
  if (!authHeader || !authHeader.startsWith('Basic ')) {
    return false;
  }

  const [username, password] = Buffer.from(authHeader.split(' ')[1], 'base64')
    .toString()
    .split(':');
  return username === THE_USERNAME && password === THE_PASSWORD;
}

Who needs Auth.js, OAuth, databases, sessions, when you have Basic Auth? lol