npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

next-pocketbase-auth

v0.1.1

Published

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

Downloads

271

Readme

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
};