Skip to main content

Authentication Overview

This application uses HTTP Basic Authentication to secure communication between the Next.js frontend and the Bun/Hono backend. All API requests include Base64-encoded credentials in the Authorization header.
Basic Authentication is suitable for this use case since the application runs over HTTPS and credentials are stored securely in environment variables.

How Basic Auth Works

Basic Authentication follows this pattern:
1

Credentials Storage

Username and password are stored in .env file as environment variables
2

Base64 Encoding

Credentials are combined as username:password and Base64-encoded
3

Authorization Header

Encoded credentials are sent in Authorization: Basic <encoded> header
4

Backend Verification

Backend decodes and verifies credentials before processing the request

Environment Variables

Credentials are stored in the .env file:
BACKEND_URL=http://localhost:3000
USERNAME=your_username
PASSWORD=your_password
Never commit the .env file to version control. Always add it to .gitignore.

Authentication in Server Components

Server Components can directly access environment variables and add authentication headers: From src/components/Messages.tsx:29-35:
export default async function Messages() {
  const data = await fetch(`${process.env.BACKEND_URL}/messages/all`, {
    headers: {
      Authorization: `Basic ${Buffer.from(
        `${process.env.USERNAME}:${process.env.PASSWORD}`
      ).toString("base64")}`,
    },
  });
  if (!data.ok) {
    notFound();
  }
  const messages = await data.json();
}
From src/components/People.tsx:17-23:
export default async function People() {
  const data = await fetch(`${process.env.BACKEND_URL}/people/all`, {
    headers: {
      Authorization: `Basic ${Buffer.from(
        `${process.env.USERNAME}:${process.env.PASSWORD}`
      ).toString("base64")}`,
    },
  });
}

Base64 Encoding Pattern

The encoding pattern is consistent across the application:
Authorization: `Basic ${Buffer.from(
  `${process.env.USERNAME}:${process.env.PASSWORD}`
).toString("base64")}`
This creates a header like:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Authentication in Server Actions

All Server Actions include authentication when making backend requests: From src/lib/actions/message/createMessage.ts:39-51:
"use server";

export default async function createMessage(
  prevState: MessagePrevState,
  formData: FormData
): Promise<MessagePrevState> {
  try {
    const response = await fetch(
      `${process.env.BACKEND_URL}/messages/create-one`,
      {
        method: "POST",
        headers: {
          Authorization: `Basic ${Buffer.from(
            `${process.env.USERNAME}:${process.env.PASSWORD}`
          ).toString("base64")}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(parsedData.data),
      }
    );

    if (!response.ok) {
      return {
        error: `Backend Error: ${response.status} ${response.statusText}`,
      };
    }
  } catch (error) {
    // Error handling
  }
}
From src/lib/actions/people/createPerson.ts:35-47:
const response = await fetch(
  `${process.env.BACKEND_URL}/people/create-one`,
  {
    method: "POST",
    headers: {
      Authorization: `Basic ${Buffer.from(
        `${process.env.USERNAME}:${process.env.PASSWORD}`
      ).toString("base64")}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(parsedData.data),
  }
);
From src/lib/actions/reschedule/reschedule.ts:13-19:
const response = await fetch(`${process.env.BACKEND_URL}/messages/reschedule`, {
    method: "GET",
    headers: {
        Authorization: `Basic ${Buffer.from(
            `${process.env.USERNAME}:${process.env.PASSWORD}`
        ).toString("base64")}`,
    },
})

Security Considerations

Environment Variables are Server-Only

Environment variables prefixed without NEXT_PUBLIC_ are only available on the server. They are never exposed to the client.
This means: Safe: Access in Server Components
export default async function ServerComponent() {
  const response = await fetch(process.env.BACKEND_URL);
}
Safe: Access in Server Actions
"use server";

export default async function serverAction() {
  const url = process.env.BACKEND_URL;
}
Not Possible: Access in Client Components
"use client";

export default function ClientComponent() {
  // process.env.BACKEND_URL is undefined here!
  const response = await fetch(process.env.BACKEND_URL);
}

HTTPS in Production

Basic Authentication sends credentials in every request. Always use HTTPS in production to encrypt these headers.
1

Development

HTTP is acceptable for local development (localhost)
2

Production

Always use HTTPS to encrypt authentication headers in transit
3

Deployment

Configure your hosting platform (Vercel, Railway, etc.) to enforce HTTPS

Error Handling

All authentication failures should be handled gracefully: From src/components/Messages.tsx:36-38:
if (!data.ok) {
  notFound();
}
From src/lib/actions/message/createMessage.ts:53-57:
if (!response.ok) {
  return {
    error: `Backend Error: ${response.status} ${response.statusText}`,
  };
}

Backend Validation

The Bun/Hono backend must validate the authentication header:
// Example backend validation (not from this repo)
app.use('*', async (c, next) => {
  const authHeader = c.req.header('Authorization');
  
  if (!authHeader?.startsWith('Basic ')) {
    return c.json({ error: 'Unauthorized' }, 401);
  }
  
  const credentials = Buffer.from(
    authHeader.slice(6),
    'base64'
  ).toString();
  
  const [username, password] = credentials.split(':');
  
  if (username !== expectedUsername || password !== expectedPassword) {
    return c.json({ error: 'Invalid credentials' }, 401);
  }
  
  await next();
});

Complete Authentication Flow

Authentication Patterns by Component Type

Server Components directly access environment variables:
export default async function ServerComponent() {
  const response = await fetch(`${process.env.BACKEND_URL}/endpoint`, {
    headers: {
      Authorization: `Basic ${Buffer.from(
        `${process.env.USERNAME}:${process.env.PASSWORD}`
      ).toString("base64")}`,
    },
  });
  const data = await response.json();
  return <div>{/* Render data */}</div>;
}
Server Actions for form submissions:
"use server";

export default async function formAction(
  prevState: State,
  formData: FormData
): Promise<State> {
  const response = await fetch(
    `${process.env.BACKEND_URL}/endpoint`,
    {
      method: "POST",
      headers: {
        Authorization: `Basic ${Buffer.from(
          `${process.env.USERNAME}:${process.env.PASSWORD}`
        ).toString("base64")}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    }
  );
  
  if (!response.ok) {
    return { error: "Authentication failed" };
  }
  
  return { success: true };
}
Server Actions triggered by button clicks:
"use server";

export default async function simpleAction(): Promise<State> {
  const response = await fetch(
    `${process.env.BACKEND_URL}/endpoint`,
    {
      headers: {
        Authorization: `Basic ${Buffer.from(
          `${process.env.USERNAME}:${process.env.PASSWORD}`
        ).toString("base64")}`,
      },
    }
  );
  
  if (!response.ok) {
    return { success: false };
  }
  
  return { success: true };
}

Creating an Authentication Utility

To reduce code duplication, create a utility function:
// src/lib/auth.ts
export function getAuthHeader() {
  return {
    Authorization: `Basic ${Buffer.from(
      `${process.env.USERNAME}:${process.env.PASSWORD}`
    ).toString("base64")}`,
  };
}
Then use it in your components and actions:
import { getAuthHeader } from "@/lib/auth";

const response = await fetch(`${process.env.BACKEND_URL}/endpoint`, {
  headers: {
    ...getAuthHeader(),
    "Content-Type": "application/json",
  },
});
This utility function must be imported only in server-side code (Server Components and Server Actions).

Troubleshooting

Possible causes:
  • Incorrect username or password in .env
  • Backend not properly decoding the Authorization header
  • Credentials not matching backend expectations
Solution:
  1. Verify .env file has correct credentials
  2. Check backend authentication middleware
  3. Test credentials with curl:
    curl -H "Authorization: Basic $(echo -n 'username:password' | base64)" \
      http://localhost:3000/messages/all
    
Possible causes:
  • Accessing environment variables in Client Components
  • .env file not in project root
  • Server not restarted after changing .env
Solution:
  1. Only access process.env in Server Components and Server Actions
  2. Ensure .env is in project root (same level as package.json)
  3. Restart the development server: npm run dev
Possible causes:
  • Backend not configured to accept requests from frontend origin
  • Missing CORS headers in backend responses
Solution: Configure CORS in your Hono backend:
import { cors } from 'hono/cors';

app.use('*', cors({
  origin: ['http://localhost:3001'],
  credentials: true,
}));

Security Best Practices

Environment Variables

Store credentials in .env and never commit to version control

HTTPS Only

Always use HTTPS in production to encrypt authentication headers

Server-Side Only

Only access credentials in Server Components and Server Actions

Error Messages

Don’t expose sensitive details in error messages returned to client

Next Steps

Server Actions

Learn how to implement server-side mutations

Configuration

Configure environment variables for production

Build docs developers (and LLMs) love