Prerequisite You should already have a Next.js app created.

Step 1. Install Protocol Auth in your NextjS app:

pnpm add @protoxyz/auth

Step 2. Create a project at https://studio.pxyz.dev

Once created, you will see environment variables for your project on the dashboard.

Step 3. Add the environment variables to your .env file:

NEXT_PUBLIC_PXYZ_TENANT_ID=...
NEXT_PUBLIC_PXYZ_PUBLIC_KEY=...
NEXT_PUBLIC_PXYZ_DOMAIN=...
PXYZ_SECRET_KEY=...

Step 4. Project setup (app router)

Add the following code to lib/auth.ts:

import { auth } from "@protoxyz/auth";
import { redirect } from "next/navigation";

interface ProtectOptions {
  role?: string;
  orgRole?: string;
}

export const protectPage = async (options?: ProtectOptions) => {
  const session = await auth();

  if (!session) {
    return redirect(getLoginUrl());
  }

  if (options?.role && session.claims?.role !== options.role) {
    return redirect("/unauthorized");
  }

  return session;
};

// Returns the URL to the hosted accounts page
export const getAccountsUrl = ({ path = "/" }: { path?: string } = {}) => {
  return new URL(path, `https://${process.env.NEXT_PUBLIC_PXYZ_DOMAIN}`);
};

// Returns the URL to the login path on the hosted accounts page
export const getLoginUrl = (redirectPath: string | undefined = "/") => {
  const url = getAccountsUrl({ path: "/sign-in" });
  const redirectUri = new URL("/api/auth/callback", getWebUrl());
  redirectUri.searchParams.set("redirectPath", redirectPath);

  url.searchParams.set("redirectUri", redirectUri.toString());

  return url.toString();
};

// Returns the URL to the sign up path on the hosted accounts page
export const getSignupUrl = (redirectPath: string | undefined = "/onboard") => {
  const url = getAccountsUrl({ path: "/sign-up" });
  const redirectUri = new URL("/api/auth/callback", getWebUrl());
  redirectUri.searchParams.set("redirectPath", redirectPath);

  url.searchParams.set("redirectUri", redirectUri.toString());

  return url.toString();
};

// Returns the URL to this page
export function getWebUrl() {
  return process.env.NODE_ENV === "production"
    ? `https://www.yourdomain.com`
    : "http://localhost:3000";
}

Whenever you need to determine whether or not a user is authenticated, you can use the auth function. If the user is not authenticated, the function will return null. If the user is authenticated, the function will return the verified JWT object.

Add an api route to handle the callback from the hosted accounts page. Create a new file called app/api/auth/callback.ts. Add the following code:

import type { NextRequest } from "next/server";

import { handleCallback } from "@protoxyz/auth";

export const GET = async (req: NextRequest) => {
  return await handleCallback(req, async ({ req, res, session }) => {
    console.log("Auth was successful!!!", session);

    // This is where you can sync the user object to your database, send a welcome email, or
    // provision initial data for the user.

    return res;
  });
};

Step 5. Protect pages (app router)

Create a new page in your project that you want to secure. For example, create a new file called app/onboard/page.tsx. Add the following code:

import { protectPage } from "@/lib/auth";

export default function OnboardPage() {
  const session = protectPage();

  return (
    <div>
      Welcome to the onboarding page! Only logged in users can see this.
    </div>
  );
}

Step 6. Protect api routes (app router)

Create a new file called app/api/protected/route.ts. Add the following code:

import { auth } from "@/lib/auth";
import { NextApiRequest, NextApiResponse } from "next";

export default async function GET(req: NextApiRequest) {
  const session = await auth();

  if (!session) {
    return res.json({ error: "Unauthorized" });
  }

  return res.json({ message: "Hello, secret message!" });
}

Step 7. Protecting server actions

You can also protect server actions by calling the auth function. For example, if your actions are in lib/actions.ts add the following code:

"use server";
import "server-only";
import { auth } from "@/lib/auth";

export async function createTask(formData: FormData) {
  const session = await auth();

  if (!session) {
    return new Response("Unauthorized", { status: 401 });
  }

  // Create a new task
}

Step 8. Checking if a user is authenticated in middleware

You can also check if a user is authenticated in middleware. For example, if you want to check if a user is authenticated in a middleware, add the following code:

import { auth } from "@/lib/auth";

export default function middleware(req: NextRequest) {
  const session = await auth();

  if (!session) {
    return new Response("Unauthorized", { status: 401 });
  }

  return NextResponse.next();
}

If you only need to check if a user is authenticated, you can use the getToken function. For example:

import { getToken } from "@protoxyz/auth";

export default function middleware(req: NextRequest) {
  const token = getToken(req);

  if (!token) {
    return new Response("No token found, user is not logged in", {
      status: 401,
    });
  }

  // User is probably authenticated, or at least has a token set

  return NextResponse.next();
}

Step 9. Passing user data to client components

You can easily pass user data to client components from the server. For example, create a new component in components/user-button.tsx. Add the following code:

import type { UserSession } from "@protoxyz/auth";
export default function UserButton({ user }: { user: UserSession["claims"] }) {
  return (
    <button>
      {user.name} - {user.email}
    </button>
  );
}

And then call it from your protected page:

import { protectPage } from "@/lib/auth";
import UserButton from "@/components/user-button";

export default function OnboardPage() {
  const session = protectPage();

  return (
    <div>
      <UserButton user={session.claims} />
    </div>
  );
}