Published on

Using Supabase Auth with Custom Middleware in Hono

Authors
  • avatar
    Name
    Shelton Ma
    Twitter

Create Middleware supabaseMiddleware

1. Features

The custom middleware provides the following functionality:

  • Attach Supabase Client: Makes the Supabase client accessible via the context (c).
  • Attach Authenticated User to Context: Attaches a supabaseAuth object (UserResponse | null) to the context.
  • Retrieve User from Cookies: Attempts to identify the user based on cookies.
  • Retrieve User from Authorization Header: Uses the Bearer token in the Authorization header if available.

2. Example Middleware

// src/utils/hono/supabase-auth/index.ts
import { createClient } from "@/utils/supabase/server";
import { User } from "@supabase/auth-js";
import { SupabaseClient } from "@supabase/supabase-js";
import { Context, MiddlewareHandler } from "hono";

export const supabaseMiddleware: MiddlewareHandler = async (c, next) => {
  // Attach Supabase client to the context
  const supabase = await createClient();
  const headers = c.req.raw.headers;

  if (headers) {
    headers.forEach((value, key) => {
      c.res.headers.append(key, value);
    });
    const locationHeader = headers.get("location");
    if (locationHeader) {
      const validUrlPattern = /^https?:\/\/[^\s/$.?#].[^\s]*$/i;
      if (validUrlPattern.test(locationHeader)) {
        c.redirect(locationHeader, 307);
        return;
      } else {
        return c.json({ error: "Invalid redirect URL" }, 400);
      }
    }
  }

  // try to get the user from the cookies
  const { data: userFromCookie, error: errorFromCookie } =
    await supabase.auth.getUser();
  if (errorFromCookie) {
    // Try to get the user from the Authorization header
    const authHeader = c.req.header("Authorization");
    if (!authHeader) {
      return c.json({ error: "Authorization header missing" }, 401);
    }
    const token = authHeader.split(" ")[1];
    if (!token) {
      return c.json({ error: "Token missing in Authorization header" }, 401);
    }
    const { data: userFromToken, error } = await supabase.auth.getUser(token);
    if (error) {
      return c.json({ error: "Unauthorized" }, 401);
    }
    c.set("supabaseAuth", userFromToken);
  } else {
    c.set("supabaseAuth", userFromCookie);
  }
  c.set("supabase", supabase);
  await next();
};

export const getAuth = (c: Context): User | null => {
  return c.get("supabaseAuth") || null;
};

export const getSupabaseClient = (c: Context): SupabaseClient => {
  return c.get("supabase");
};

Usage

1. Globally Enable supabaseMiddleware

// src/app/api/[[...route]]/route.ts
import { supabaseMiddleware } from "@/utils/hono/supabase-auth";

const app = new Hono().basePath("/api");
app.use("*", supabaseMiddleware);

2. Enable supabaseMiddleware for Specific Routes

import { supabaseMiddleware } from "@/utils/hono/supabase-auth";
const app = new Hono()
  .get("/", supabaseMiddleware, (c) => {
    ...
    return c.json({ data });
  })

Access Authenticated User

import { getAuth } from "@/utils/hono/supabase-auth";

const user_id = getAuth(c)?.id;
if (!user_id) {
  return c.json({ error: "Unauthorized" }, 401);
}