Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/g12i/next-pocketbase-auth

Wrapper for Next.js applications using PocketBase, with support for both client and server components
https://github.com/g12i/next-pocketbase-auth

Last synced: about 1 month ago
JSON representation

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

Awesome Lists containing this project

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

```bash
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:

```ts
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:

```ts
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::

```ts
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).

```ts
// 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](https://pocketbase.io/docs/authentication/), 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:

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

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

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

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

return (


Login with GitHub

);
}
```

### 5. Access user info from Server Components

```tsx
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

Hello {result.record.name}

;
}
```

## 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:

```ts
// 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](https://github.com/patmood/pocketbase-typegen) package.

Use it as:

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

const pb = createServerClient();
const pb2 = createBrowserClient();
```

### API

#### `createBrowserClient(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(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:

```ts
export const defaultCookieOptions: CookieOptions = {
httpOnly: false,
secure: process.env.NODE_ENV === "production",
sameSite: "strict" as const,
path: "/",
expires: new Date(Date.now() + 1209600), // 14 days
};
```