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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@artatol-acp/auth-nextjs

v0.7.0

Published

Next.js SDK for Artatol Cloud Platform Authentication with support for App Router, Server Actions, and Middleware

Downloads

156

Readme

@artatol-acp/auth-nextjs

Next.js SDK for Artatol Cloud Platform Authentication with support for App Router, Server Actions, and automatic token refresh.

Installation

pnpm add @artatol-acp/auth-nextjs

Prerequisites

Before using this SDK, you need from the ACP AUTH service:

  1. Base URL - The auth service URL (e.g., https://sso.artatol.net)
  2. API Key - Required for token refresh in proxy/middleware
  3. JWT Public Key - For local JWT verification. Get it from:
    curl https://sso.artatol.net/public-key > public.pem

How Authentication Works

The SDK uses httpOnly cookies for secure token storage:

  • access_token - Short-lived JWT (5 minutes), used for API calls
  • refresh_token - Long-lived token (7 days), used to get new access tokens

Important: The auth server rotates refresh tokens on each use. After a refresh, the old refresh token is invalidated and a new one is issued.

Quick Start

1. Set Up Proxy/Middleware (REQUIRED)

CRITICAL: The proxy is required for automatic token refresh. Without it, users will be logged out when their access token expires (every 5 minutes).

Why is proxy required?

In Next.js App Router, cookies can only be modified in:

  • Route Handlers (app/api/...)
  • Server Actions
  • Middleware/Proxy

Server Components cannot set cookies. When a user visits a protected page with an expired access token, the page render cannot refresh the token. The proxy intercepts the request before the page renders and handles the refresh.

Create the proxy file:

| Next.js Version | File Location | |-----------------|---------------| | 16+ | src/proxy.ts or proxy.ts (root) | | 14-15 | src/middleware.ts or middleware.ts (root) |

Note: If your project uses a src/ directory, the file must be inside src/.

// src/proxy.ts (Next.js 16+)
// or src/middleware.ts (Next.js 14-15)

import { NextRequest, NextResponse } from 'next/server'
import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/proxy'
// For Next.js 14-15: import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/middleware'

const acpMiddleware = createACPAuthMiddleware({
  baseUrl: process.env.ACP_AUTH_URL!,
  apiKey: process.env.ACP_AUTH_API_KEY!,
  jwtPublicKey: process.env.ACP_AUTH_JWT_PUBLIC_KEY!,
  publicPaths: ['/login', '/register', '/forgot-password', '/reset-password', '/verify-email'],
  loginPath: '/login',
  cookies: {
    domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined,
  },
})

export default async function middleware(request: NextRequest) {
  // Homepage is public - handle with exact match
  if (request.nextUrl.pathname === '/') {
    return NextResponse.next()
  }

  return acpMiddleware(request)
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico|icon).*)'],
}

WARNING about publicPaths:

The publicPaths option uses prefix matching (pathname.startsWith(path)). This means:

  • /login matches /login, /login/, /login/callback, etc.
  • NEVER include just / in publicPaths - it would match ALL paths!

Always handle the homepage (/) with an exact match check as shown above.

2. Create API Route Handlers

Create app/api/auth/[action]/route.ts:

import { createAuthHandlers } from '@artatol-acp/auth-nextjs/handlers'

const { authHandler } = createAuthHandlers({
  baseUrl: process.env.ACP_AUTH_URL!,
  apiKey: process.env.ACP_AUTH_API_KEY,
  cookies: {
    domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined,
  },
})

export const POST = authHandler

This creates these endpoints:

  • POST /api/auth/login - Login
  • POST /api/auth/logout - Logout
  • POST /api/auth/session - Refresh session & get user (used by client-side refresh)
  • POST /api/auth/register - Register
  • POST /api/auth/verify-2fa - Complete 2FA login
  • POST /api/auth/resend-verification - Resend verification email
  • POST /api/auth/forgot-password - Request password reset
  • POST /api/auth/reset-password - Reset password

3. Initialize Server-side Auth

Create lib/auth.ts:

import { initACPAuth } from '@artatol-acp/auth-nextjs/server'

// Initialize once at module load
initACPAuth({
  baseUrl: process.env.ACP_AUTH_URL!,
  apiKey: process.env.ACP_AUTH_API_KEY,
  jwtPublicKey: process.env.ACP_AUTH_JWT_PUBLIC_KEY!,
  cookies: {
    domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined,
  },
})

// Re-export server functions
export {
  getUser,
  me,
  login,
  logout,
  register,
  verify2FALogin,
  verifyEmail,
  resendVerificationEmail,
  forgotPassword,
  resetPassword,
  deleteAccount,
  refreshAccessToken,
} from '@artatol-acp/auth-nextjs/server'

4. Add Client Provider

In your root layout (app/layout.tsx):

import { ACPAuthProvider } from '@artatol-acp/auth-nextjs/client'
import { getUser } from '@/lib/auth'

export default async function RootLayout({ children }) {
  // getUser() is fast - just verifies JWT locally, no API call
  // Returns { id, email } or null
  const user = await getUser()

  return (
    <html>
      <body>
        <ACPAuthProvider initialUser={user}>
          {children}
        </ACPAuthProvider>
      </body>
    </html>
  )
}

How client-side refresh works:

When initialUser is null (no access_token or expired), ACPAuthProvider automatically calls /api/auth/session to attempt a refresh using the refresh_token. If successful, the user state updates and new cookies are set. This enables seamless client-side navigation even when access_token has expired.

Usage

Server Components

import { getUser, me } from '@/lib/auth'

export default async function ProfilePage() {
  // Fast local JWT verification (returns { id, email } or null)
  const user = await getUser()

  // Or fetch full user from API (returns { id, email, twoFactorEnabled } or null)
  const fullUser = await me()

  if (!user) {
    return <div>Not logged in</div>
  }

  return <div>Welcome {user.email}</div>
}

When to use getUser() vs me():

| Function | Speed | Returns | Use when | |----------|-------|---------|----------| | getUser() | Fast (local JWT) | { id, email } | You only need basic user info | | me() | Slower (API call) | { id, email, twoFactorEnabled } | You need twoFactorEnabled status |

Server Actions

'use server'

import { login, logout, verify2FALogin } from '@/lib/auth'
import { redirect } from 'next/navigation'

export async function loginAction(formData: FormData) {
  const email = formData.get('email') as string
  const password = formData.get('password') as string

  const result = await login(email, password)

  if ('requiresTwoFactor' in result) {
    return { requires2FA: true, tempToken: result.tempToken }
  }

  redirect('/dashboard')
}

export async function logoutAction() {
  await logout()
  redirect('/login')
}

Client Components

'use client'

import { useAuth } from '@artatol-acp/auth-nextjs/client'

export function UserMenu() {
  const { user, logout, isLoading } = useAuth()

  if (isLoading) return <div>Loading...</div>
  if (!user) return <a href="/login">Sign In</a>

  return (
    <div>
      <span>{user.email}</span>
      <button onClick={logout}>Logout</button>
    </div>
  )
}

Environment Variables

ACP_AUTH_URL=https://sso.artatol.net
ACP_AUTH_API_KEY=your-api-key
ACP_AUTH_JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"

API Reference

Proxy Options

createACPAuthMiddleware({
  baseUrl: string,           // Auth server URL (required)
  apiKey: string,            // API key for refresh calls (required)
  jwtPublicKey: string,      // PEM public key for JWT verification (required)
  publicPaths?: string[],    // Paths that don't require auth (default: ['/login', '/register', ...])
  loginPath?: string,        // Redirect path for unauthenticated users (default: '/login')
  cookies?: {
    domain?: string,         // Cookie domain for SSO (e.g., '.yourdomain.com')
    path?: string,           // Cookie path (default: '/')
    secure?: boolean,        // HTTPS only (default: NODE_ENV === 'production')
    sameSite?: 'strict' | 'lax' | 'none', // (default: 'lax')
  },
})

Server Functions

| Function | Description | |----------|-------------| | initACPAuth(options) | Initialize auth configuration (call once) | | getUser() | Get user from JWT (local, fast) -> { id, email } or null | | me() | Get user from API (full data) -> { id, email, twoFactorEnabled } or null | | login(email, password) | Login, sets cookies -> { accessToken, user } or { requiresTwoFactor, tempToken } | | verify2FALogin(tempToken, code) | Complete 2FA login | | logout() | Logout, clears cookies | | register(email, password) | Register new user | | verifyEmail(token) | Verify email address | | resendVerificationEmail(email) | Resend verification email | | forgotPassword(email) | Request password reset | | resetPassword(token, password) | Reset password | | deleteAccount(password, confirmation) | Delete account | | refreshAccessToken() | Manually refresh access token |

Client Hook

const {
  user,           // Current user or null
  isLoading,      // True during initial load or refresh
  login,          // (email, password) => Promise<{ requiresTwoFactor?, tempToken? }>
  verify2FA,      // (tempToken, code) => Promise<void>
  logout,         // () => Promise<void>
  refresh,        // () => Promise<boolean> - manually trigger session refresh
  resendVerification, // (email) => Promise<void>
} = useAuth()

ACPAuthProvider Props

<ACPAuthProvider
  apiBasePath="/api/auth"  // Optional, default: "/api/auth"
  initialUser={user}       // Optional, from getUser() - { id, email } or null
>
  {children}
</ACPAuthProvider>

Token Refresh Flow

Server-side (hard refresh, direct URL access)

  1. User visits protected page with expired access_token
  2. Proxy intercepts the request before page renders
  3. Proxy calls auth server /refresh with refresh_token
  4. Auth server validates, rotates refresh token, returns new tokens
  5. Proxy sets new access_token and refresh_token cookies
  6. Page renders with valid tokens

Client-side (navigation within app)

  1. User clicks link to protected page (client-side navigation)
  2. Proxy does NOT run (client navigation doesn't hit server)
  3. ACPAuthProvider detects initialUser is null
  4. Calls /api/auth/session which refreshes tokens and sets cookies
  5. User state updates, navigation proceeds

If API handlers are missing: Client-side refresh won't work, user gets logged out on navigation when access_token expires.

If proxy is missing: Server-side refresh won't work, user gets logged out on hard refresh when access_token expires.

Error Handling

import { ACPAuthError } from '@artatol-acp/auth-nextjs/server'

try {
  await login(email, password)
} catch (error) {
  if (error instanceof ACPAuthError) {
    console.error('Status:', error.statusCode)
    console.error('Message:', error.message)
    console.error('Code:', error.code)
  }
}

| Status | Meaning | |--------|---------| | 400 | Validation error (bad input) | | 401 | Unauthorized (invalid credentials/token) | | 403 | Forbidden (email not verified, account locked) | | 429 | Too Many Requests (rate limited) |

Password Requirements

  • Minimum 10 characters
  • At least one lowercase letter (a-z)
  • At least one uppercase letter (A-Z)
  • At least one number (0-9)

SSO Configuration

For single sign-on across subdomains (e.g., app.example.com and admin.example.com), set the cookie domain:

// In proxy/middleware, handlers, and server init:
cookies: {
  domain: '.example.com', // Note the leading dot
}

License

MIT