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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@nightmar3/uauth-next

v1.0.6

Published

Next.js integration for Universal Auth SDK - Zero config authentication

Downloads

593

Readme

@nightmar3/uauth-next

Next.js integration for Universal Auth SDK. Provides seamless authentication with minimal setup.

[!IMPORTANT] Backend Required: This SDK requires a backend API. See Backend Requirements for the complete API contract, or use our Backend Implementation Guide for step-by-step instructions.

Setup Guide

Step 1: Install the Package

npm install @nightmar3/uauth-next

Step 2: Set Environment Variable

Create a .env.local file in your Next.js project root:

NEXT_PUBLIC_AUTH_URL=http://localhost:8000/auth

Step 3: Add AuthProvider to Layout

Wrap your application with the AuthProvider:

// app/layout.tsx
import { AuthProvider } from '@nightmar3/uauth-next'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <AuthProvider>{children}</AuthProvider>
      </body>
    </html>
  )
}

With OAuth (optional): If you need OAuth authentication, pass plugins:

import { AuthProvider, createOAuth2Plugin } from '@nightmar3/uauth-next'

const plugins = [createOAuth2Plugin()]

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AuthProvider plugins={plugins}>{children}</AuthProvider>
      </body>
    </html>
  )
}

Step 4: Create Login Page

// app/login/page.tsx
'use client'

import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { useAuth } from '@nightmar3/uauth-next'

export default function LoginPage() {
  const router = useRouter()
  const { user, isLoading, signIn } = useAuth()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState<string | null>(null)

  // Redirect if already logged in
  useEffect(() => {
    if (user) {
      router.replace('/dashboard')
    }
  }, [user, router])

  if (isLoading || user) {
    return <div>Loading...</div>
  }

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    const result = await signIn(email, password)
    if (result.ok) {
      router.replace('/dashboard')
    } else {
      setError(result.error?.message || 'Sign in failed')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      {error && <p>{error}</p>}
      <button type="submit">Sign In</button>
    </form>
  )
}

Step 5: Create Protected Dashboard

// app/dashboard/page.tsx
import { getSession } from '@nightmar3/uauth-next/server'
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  const session = await getSession()

  if (!session) {
    redirect('/login')
  }

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Welcome, {session.user.name || session.user.email}</p>
    </div>
  )
}

Step 6: Add Sign Out Button

// components/sign-out-button.tsx
'use client'

import { useRouter } from 'next/navigation'
import { useAuth } from '@nightmar3/uauth-next'

export function SignOutButton() {
  const router = useRouter()
  const { signOut } = useAuth()

  const handleSignOut = async () => {
    await signOut()
    router.replace('/login')
  }

  return <button onClick={handleSignOut}>Sign Out</button>
}

Step 7 (Optional): Add Route Protection Middleware

// middleware.ts
import { authMiddleware } from '@nightmar3/uauth-next/middleware'

export default authMiddleware({
  protectedRoutes: ['/dashboard', '/dashboard/**', '/settings/**'],
  loginUrl: '/login',
})

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

That's it! Your Next.js app now has full authentication support.


Use Cases

Email/Password Authentication

Sign In

'use client'

import { useAuth } from '@nightmar3/uauth-next'

function LoginForm() {
  const { signIn } = useAuth()

  const handleSubmit = async (email: string, password: string) => {
    const result = await signIn(email, password)
    if (!result.ok) {
      console.error(result.error?.message)
    }
  }
}

Sign Up

'use client'

import { useAuth } from '@nightmar3/uauth-next'

function SignUpForm() {
  const { signUp } = useAuth()

  const handleSubmit = async (email: string, password: string, name: string) => {
    const result = await signUp({ email, password, name })
    if (!result.ok) {
      console.error(result.error?.message)
    }
  }
}

OAuth Authentication (Optional)

OAuth is completely optional. If you don't need OAuth, skip this section entirely - no extra network requests will be made.

No OAuth? No problem. Just use signIn and signUp for email/password authentication.

To add OAuth support:

  1. Add the OAuth2 plugin to your provider:
// app/layout.tsx or components/providers.tsx
import { AuthProvider, createOAuth2Plugin } from '@nightmar3/uauth-next'

const plugins = [createOAuth2Plugin()]

export function Providers({ children }) {
  return <AuthProvider plugins={plugins}>{children}</AuthProvider>
}
  1. Use the useOAuth hook in your login page:
'use client'

import { useOAuth } from '@nightmar3/uauth-next'

function OAuthLogin() {
  const { providers, isLoading, signInWithOAuth } = useOAuth()

  // No OAuth configured on backend - don't render anything
  if (providers.length === 0) return null

  if (isLoading) return <div>Loading...</div>

  return (
    <div>
      {providers.map((provider) => (
        <button
          key={provider.name}
          onClick={() => signInWithOAuth(provider.name)}
        >
          Continue with {provider.displayName}
        </button>
      ))}
    </div>
  )
}
  1. Create a callback page for OAuth redirects:
// app/auth/callback/page.tsx
'use client'

import { Suspense, useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { useAuth } from '@nightmar3/uauth-next'

export default function CallbackPage() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <CallbackHandler />
    </Suspense>
  )
}

function CallbackHandler() {
  const router = useRouter()
  const searchParams = useSearchParams()
  const { user } = useAuth()
  const error = searchParams.get('error')

  useEffect(() => {
    if (user) router.replace('/dashboard')
  }, [user, router])

  if (error) {
    return <div>Error: {error}</div>
  }

  return <div>Completing sign in...</div>
}

Server-Side Authentication

// app/profile/page.tsx
import { getSession, getUser } from '@nightmar3/uauth-next/server'
import { redirect } from 'next/navigation'

export default async function ProfilePage() {
  const session = await getSession()

  if (!session) {
    redirect('/login')
  }

  return (
    <div>
      <h1>{session.user.name}</h1>
      <p>{session.user.email}</p>
    </div>
  )
}

Conditional UI Rendering

import { SignedIn, SignedOut, AuthGate } from '@nightmar3/uauth-next'

function Header() {
  return (
    <nav>
      <SignedIn>
        <UserMenu />
      </SignedIn>
      <SignedOut>
        <LoginButton />
      </SignedOut>
    </nav>
  )
}

function ProtectedContent() {
  return (
    <AuthGate
      loading={<Spinner />}
      fallback={<p>Please sign in</p>}
    >
      <SecretContent />
    </AuthGate>
  )
}

Custom User Type

interface MyUser {
  id: string
  email: string
  name: string
  role: 'admin' | 'user'
}

// Client
const { user } = useAuth<MyUser>()

// Server
const session = await getSession<MyUser>()

Token Refresh

Auto-refresh (Client-side)

By default, tokens are automatically refreshed before they expire. This happens silently in the background.

// Default: auto-refresh enabled, refreshes 60 seconds before expiry
<AuthProvider>
  <App />
</AuthProvider>

// Customize when to refresh (e.g., 5 minutes before expiry)
<AuthProvider refreshBeforeExpiry={300}>
  <App />
</AuthProvider>

// Disable auto-refresh (not recommended)
<AuthProvider autoRefresh={false}>
  <App />
</AuthProvider>

How it works:

  1. When user signs in or session loads, a timer is set based on token expiry
  2. Token is refreshed automatically before it expires
  3. On sign out, the timer is cleared
  4. If refresh fails, the next API request will trigger a 401 → automatic refresh retry

Manual client-side refresh

'use client'

import { useAuth } from '@nightmar3/uauth-next'

function RefreshButton() {
  const { refreshSession } = useAuth()

  const handleRefresh = async () => {
    await refreshSession()
    // Tokens are refreshed and user data is reloaded
  }

  return <button onClick={handleRefresh}>Refresh Session</button>
}

Server-side refresh

// app/api/refresh/route.ts
import { refreshToken } from '@nightmar3/uauth-next/server'

export async function POST() {
  const result = await refreshToken()

  if (!result.ok) {
    return new Response('Token refresh failed', { status: 401 })
  }

  return Response.json({ ok: true })
}

Auto-refresh on server

getSession() automatically handles token refresh when it receives a 401 (unauthorized) response. If the access token is expired, it will:

  1. Attempt to refresh tokens using the refresh token
  2. Update the cookies with new tokens
  3. Retry the session request with the new access token

This happens transparently - you just use getSession():

// app/dashboard/page.tsx
import { getSession } from '@nightmar3/uauth-next/server'
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  // Automatically refreshes tokens on 401
  const session = await getSession()

  if (!session) {
    redirect('/login')
  }

  return <div>Welcome {session.user.name}</div>
}

Backend Requirements

The SDK expects a backend API that implements the following endpoints. You can use our FastAPI reference implementation or build your own.

Endpoints

| Method | Path | Description | Request Body | Response Data | |--------|------|-------------|--------------|---------------| | POST | /sign-in/password | Sign in with email/password | { email, password } | { user, tokens } | | POST | /sign-in/oauth2 | Exchange OAuth code for tokens | { provider, code, redirect_uri } | { user, tokens } | | POST | /sign-up | Create new account | { email, password, name? } | { user, tokens } | | DELETE| /session | Sign out (revoke tokens) | - | { ok: boolean } | | GET | /session | Get current user session | - | { user } | | POST | /token/refresh | Refresh access token | { refresh_token } | { tokens } | | GET | /providers | List available OAuth providers | - | { providers } |

Response Format

All API responses must follow this envelope structure:

interface ApiResponse<T> {
  ok: boolean
  data: T | null
  error: {
    code: string
    message: string
    details?: any
  } | null
}

Token Structure

The backend must return tokens in this format:

interface AuthTokens {
  access_token: string
  refresh_token: string
  expires_in: number // seconds
}

OAuth Provider Response (Optional)

If implementing OAuth support, the /providers endpoint must return:

interface OAuth2Provider {
  name: string
  displayName: string
  clientId: string
  authorizationUrl: string
  scope?: string
}

Example:

{
  "ok": true,
  "data": {
    "providers": [
      {
        "name": "google",
        "displayName": "Google",
        "clientId": "your-google-client-id",
        "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
        "scope": "openid email profile"
      }
    ]
  },
  "error": null
}

API Reference

Client (@nightmar3/uauth-next)

AuthProvider

| Prop | Type | Default | Description | |------|------|---------|-------------| | plugins | Plugin[] | [] | Plugins to install (e.g., OAuth2) | | loadOnMount | boolean | true | Load session on mount | | autoRefresh | boolean | true | Auto-refresh tokens before expiry | | refreshBeforeExpiry | number | 60 | Seconds before expiry to refresh |

useAuth()

Returns:

| Property | Type | Description | |----------|------|-------------| | user | User \| null | Current user | | isLoading | boolean | Loading state | | isAuthenticated | boolean | Auth status | | error | Error \| null | Auth error | | signIn | (email, password) => Promise | Sign in | | signUp | (data) => Promise | Create account | | signOut | () => Promise | Sign out | | refreshSession | () => Promise | Refresh tokens and reload session | | auth | UniversalAuth | SDK instance | | getPlugin | (name) => Plugin | Get installed plugin |

useOAuth() (requires OAuth2 plugin)

Returns:

| Property | Type | Description | |----------|------|-------------| | providers | OAuth2Provider[] | Available OAuth providers | | isLoading | boolean | Providers loading state | | signInWithOAuth | (provider) => Promise | Sign in with OAuth |

useUser()

Returns User | null

useSession()

Returns { user, accessToken } | null

SignedIn / SignedOut

Render children based on auth state.

AuthGate

| Prop | Type | Description | |------|------|-------------| | children | ReactNode | Shown when authenticated | | fallback | ReactNode | Shown when not authenticated | | loading | ReactNode | Shown while loading |

Server (@nightmar3/uauth-next/server)

getSession()

Get the current session. Automatically refreshes tokens on 401 response.

const session = await getSession<User>()
// Returns: { user, accessToken } | null
// Automatically refreshes tokens if 401 received

getSessionWithRefresh() (deprecated)

Deprecated: Use getSession() instead - it now auto-refreshes tokens on 401.

// Old way (deprecated)
const session = await getSessionWithRefresh<User>()

// New way (recommended)
const session = await getSession<User>()

getUser()

const user = await getUser<User>()
// Returns: User | null

auth()

const { user, isAuthenticated } = await auth<User>()

refreshToken()

Manually refresh tokens on the server side.

const result = await refreshToken()

if (result.ok) {
  console.log('Tokens refreshed:', result.tokens)
} else {
  console.error('Refresh failed:', result.error)
}

Middleware (@nightmar3/uauth-next/middleware)

authMiddleware(options)

| Option | Type | Description | |--------|------|-------------| | protectedRoutes | string[] | Routes requiring auth | | publicRoutes | string[] | Always public routes | | loginUrl | string | Redirect URL for unauth | | afterLoginUrl | string | Redirect after login |

Route patterns:

  • /path - Exact match
  • /path/* - One level deep
  • /path/** - All nested paths

Environment Variables

| Variable | Required | Description | |----------|----------|-------------| | NEXT_PUBLIC_AUTH_URL | Yes | Auth API URL (client) | | AUTH_URL | No | Auth API URL (server, defaults to above) |