Skip to content

Wrapper for Next.js applications using PocketBase, with support for both client and server components

License

Notifications You must be signed in to change notification settings

g12i/next-pocketbase-auth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

next-pocketbase-auth

A lightweight authentication wrapper for Next.js applications using PocketBase, providing easy-to-use utilities for handling user session in both client and server components.

Installation

npm install next-pocketbase-auth

Security Considerations

Don't rely solely on pb.authStore.record inside server code such as middleware. It isn't guaranteed to revalidate the Auth token.

Always use await pb.collection("users").authRefresh(); to protect pages and user data.

Use following code to safely obtain information about the current user:

const pb = createServerClient(await cookies());

const { record: user } = await pb.collection("users").authRefresh();

Setup Guide

1. Environment Variables

Add your PocketBase URL to your environment variables:

NEXT_PUBLIC_PB_URL=http://127.0.0.1:8090/api/

2. Initialize PocketBase Clients

You'll need two different clients for client-side and server-side operations:

Client Component

For Client Components:

import { createBrowserClient } from "next-pocketbase-auth";

const pb = createBrowserClient();

createBrowserClient uses a singleton pattern, so you only ever create one instance, no matter how many times you call your createClient function.

Server Components

For Server Components, Server Actions, and Route Handlers::

import { createServerClient } from "next-pocketbase-auth";
import { cookies } from "next/headers";

const pb = createServerClient(await cookies());

3. Configure Middleware

The middleware is essential for maintaining authentication state across your application.

It handles:

  • Automatic token refresh
  • Cookie management
  • Authentication state persistence

Create a middleware.ts file in your project root (or in the src/ folder if that's where your code is).

// middleware.ts

import { createServerClient } from "next-pocketbase-auth";
import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const response = NextResponse.next({ request });

  const pb = createServerClient({
    get: (name) => request.cookies.get(name),
    set: (name, value, opts) => {
      request.cookies.set(name, value);
      response.cookies.set(name, value, opts);
    },
    delete: (name) => {
      request.cookies.delete(name);
      response.cookies.delete(name);
    },
  });

  // If we have a valid token, refresh the token
  try {
    if (pb.authStore.isValid) await pb.collection("users").authRefresh();
  } catch {
    // If we can't refresh the token, clear the cookies
    pb.authStore.clear();
  }

  // If we have a user, continue
  if (pb.authStore.record) return response;

  // If we are already on the login page, continue
  if (request.nextUrl.pathname === "/login") return response;

  // Redirect to the login page
  const url = request.nextUrl.clone();
  url.pathname = "/login";
  return NextResponse.redirect(url);
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico, sitemap.xml, robots.txt (metadata files)
     */
    "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
  ],
};

4. Login Page

You can uses regular PocketBase's SDK authentication functions see here, as long as you:

  • create the client with createBrowserClient() function
  • refresh the page (or redirect) once the authentication is finished

For example to login with GitHub:

export function LoginForm(): React.ReactElement {
  const [submitError, setSubmitError] = useState<string>("");
  const router = useRouter();

  const handleGitHubLogin = async () => {
    try {
      setSubmitError("");

      await pb.collection("users").authWithOAuth2({ provider: "github" });

      router.push("/");
    } catch {
      setSubmitError("An unexpected error occurred");
    }
  };

  return (
    <Button variant="outline" className="w-full" onClick={handleGitHubLogin}>
      <GitHubLogoIcon className="mr-2" />
      Login with GitHub
    </Button>
  );
}

5. Access user info from Server Components

import { to } from "await-to-js";
import { createServerClient } from "next-pocketbase-auth";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

export default async function AccountPage() {
  const pb = createServerClient(await cookies());
  const [error, result] = await to(pb.collection("users").authRefresh());

  if (error) {
    redirect("/login");
  }

  return <p>Hello {result.record.name}</p>;
}

Common Patterns

Mixed Public/Private Routes

For applications with both public and protected routes the middleware can stop at refreshing the auth token if it's present:

// middleware.ts

import { createServerClient } from "next-pocketbase-auth";
import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const response = NextResponse.next({ request });

  const pb = createServerClient({
    get: (name) => request.cookies.get(name),
    set: (name, value, opts) => {
      request.cookies.set(name, value);
      response.cookies.set(name, value, opts);
    },
    delete: (name) => {
      request.cookies.delete(name);
      response.cookies.delete(name);
    },
  });

  // If we have a valid token, refresh the token
  try {
    if (pb.authStore.isValid) await pb.collection("users").authRefresh();
  } catch {
    // If we can't refresh the token, clear the cookies
    pb.authStore.clear();
  }

  return response;
}

Using with TypeScript

Add type safety using pocketbase-typegen package.

Use it as:

import { createServerClient, createBrowserClient } from "next-pocketbase-auth";
import { TypedPocketBase } from "./lib/pb.generated";

const pb = createServerClient<TypedPocketBase>();
const pb2 = createBrowserClient<TypedPocketBase>();

API

createBrowserClient<T extends PocketBase = PocketBase>(baseUrl?: string, lang?: string, cookieOptions?: CookieOptions): T

Creates a PocketBase client instance for use in client components.

Parameters:

  • baseUrl - (optional) PocketBase API URL (defaults to process.env.NEXT_PUBLIC_PB_URL or http://127.0.0.1:8090/api/)
  • lang - (optional) language code passed to PocketBase client
  • cookieOptions - (optional) options to set cookies (see below)

Returns:

  • PocketBase client instance

createServerClient<T extends PocketBase = PocketBase>(cookies: CookiesAdapter, baseUrl?: string, lang?: string, cookieOptions?: CookieOptions): T

Creates a PocketBase client instance for use in server components.

Parameters:

  • cookies - required server cookies (see examples)
  • baseUrl - (optional) PocketBase API URL (defaults to process.env.NEXT_PUBLIC_PB_URL or http://127.0.0.1:8090/api/)
  • lang - (optional) language code passed to PocketBase client
  • cookieOptions - (optional) options to set cookies (see below)

Returns:

  • PocketBase client instance
CookieOptions

By default the cookie is set with following settings:

export const defaultCookieOptions: CookieOptions = {
  httpOnly: false,
  secure: process.env.NODE_ENV === "production",
  sameSite: "strict" as const,
  path: "/",
  expires: new Date(Date.now() + 86400), // will be set to the token expiration date, this is safe default of 24 hours
};

About

Wrapper for Next.js applications using PocketBase, with support for both client and server components

Resources

License

Stars

Watchers

Forks