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

@inertiapixel/nextjs-auth

v2.0.1

Published

Authentication system for Next.js. Supports credentials and social login, JWT token management, and lifecycle hooks — designed to integrate with nodejs-auth for full-stack MERN apps.

Readme

InertiaPixel nextjs-auth is an open-source authentication system for Next.js. It handles credential and social login flows, maintains persistent auth state, manages redirects and protected routes, and provides hooks for login/logout lifecycle events — designed to integrate seamlessly with nodejs-auth for full-stack MERN apps.

⚡ Secure by default: Tokens are stored in HttpOnly cookies.

No localStorage or sessionStorage needed, making it XSS-safe.

npm MIT License PRs Welcome Open Source TypeScript


Table of Contents


Why This Exists

While building a MERN stack project, I couldn't find a well-structured package that handled both frontend and backend authentication together. Most libraries focused on either the client or the server—rarely both.

So I decided to create a pair of authentication packages under the inertiapixel scope—one for the frontend and one for the backend—designed to work seamlessly together. If you're looking for a complete authentication solution for your MERN stack project, these paired packages are for you.

🔗 Use `@inertiapixel/nextjs-auth` on the frontend

🔗 Use `@inertiapixel/nodejs-auth` on the backend

Features

  • Secure by Default (HttpOnly cookies)
  • Auth context provider with typed hooks (useAuth)
  • Credential-based login (email & password)
  • Plug-and-play support for multiple OAuth providers (Google, Facebook, LinkedIn, etc.)
  • JWT-based session handling
  • Hook system to extend behavior (logging, analytics, audit, etc.)
  • Built-in loading state, error state, and redirect logic
  • Works perfectly with @inertiapixel/nodejs-auth backend package
  • Prebuilt components: <AuthProvider>, <Protect>, <SignedIn>, <SignedOut>, etc.
  • Lifecycle hooks (onLoginSuccess, onLoginFail, etc.)
  • Fully customizable with your own UI

Installation

npm version

npm install @inertiapixel/nextjs-auth

Environment Variables

Make sure to define these in your .env file:

NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXTAUTH_SECRET="QCqxa1M1BDk0G1WKbI+XVljv4UzCwws0dw9g5Rjb6o0="

JWT_SECRET="TBh9o9kDej2NFO3Bcab03sIiJWXNocpSIGkpdX77ARM="

NEXT_PUBLIC_API_BASE_URL=http://localhost:4000

#Social OAuth
NEXT_PUBLIC_GOOGLE_CLIENT_ID=949710049149-sacfunjq40ib8aatv2r0gg11c79hablk.apps.googleusercontent.com
NEXT_PUBLIC_FACEBOOK_CLIENT_ID=993713672922221
NEXT_PUBLIC_LINKEDIN_CLIENT_ID=86eg5q2n2qfqde

Quick Start

Make AuthProvider

//providers.tsx
'use client';

import React from 'react';
import { AuthProvider } from '@inertiapixel/nextjs-auth';

export function Providers({ children }: { children: React.ReactNode }) {

  return (
    <AuthProvider
      config={{
        apiBaseUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
        apiEndpoints: {
          //Customize as per api
          login: '/auth/login',
          refresh: '/auth/refresh',
          logout: '/auth/logout',
        },

        tokenKey: 'access_token',

        socialProviders: [
          {
            provider: 'google',
            clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || ''
          },
          {
            provider: 'facebook',
            clientId: process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID || ''
          },
          {
            provider: 'linkedin',
            clientId: process.env.NEXT_PUBLIC_LINKEDIN_CLIENT_ID || ''
          },
        ],

        onLoginSuccess: (user) => {
          console.log('Login success:', user);
        },
        onLoginFail: (error) => {
          console.error('Login failed:', error);
        },
        onLogout: () => {
          console.log('User logged out');
        },
      }}
    >
      {children}
    </AuthProvider>
  );
}

Wrap your app with the AuthProvider:

// layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Providers } from './providers'

import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {

  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <>
          <Providers>{children}</Providers>
        </>
      </body>
    </html>
  );
}

Next.js API Setup (App Router)

To handle OAuth callback redirects from social providers, you must add the following API route to your Next.js app:

// app/api/auth/[...provider]/route.ts

import { handlers } from '@inertiapixel/nextjs-auth';

export const GET = handlers({
  apiBaseUrl: process.env.NEXT_PUBLIC_API_BASE_URL!
});

Usage

Access auth state anywhere using the useAuth() hook:

//orders/page.tsx or dashboard/page.tsx (its protected page)
'use client';

import { useAuth, withAuth } from '@inertiapixel/nextjs-auth';
import type { ReactElement, FC } from 'react';

const OrdersPage: FC = (): ReactElement => {
  const { logout, user } = useAuth();

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold">Orders</h1>
      <p>Welcome to your secure orders.</p>

      <pre>
        {JSON.stringify(user)}
      </pre>
      <button
        onClick={logout}
        className="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
      >
        Logout
      </button>
    </div>
  );
};

export default withAuth(OrdersPage);

Login

Login page example

//login/page.txs
'use client';

import { useState } from 'react';
import { useAuth } from '@inertiapixel/nextjs-auth';

const LoginPage = () => {
  const { login, socialLogin, loginError, loading } = useAuth();
  const [email, setEmail] = useState('[email protected]');
  const [password, setPassword] = useState('123456789');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await login({ provider: 'credentials', email, password });
  };

  const handleGoogleLogin = async () => {
    await socialLogin('google');
  };

  const handleFacebookLogin = async () => {
    await socialLogin('facebook');
  };

  return (
    <div className="max-w-sm mx-auto mt-20 p-6 border rounded shadow">
      <h1 className="text-2xl font-bold mb-4">Login</h1>

      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label className="block text-sm font-medium">Email</label>
          <input
            type="email"
            className="w-full border px-3 py-2 rounded"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </div>

        <div>
          <label className="block text-sm font-medium">Password</label>
          <input
            type="password"
            className="w-full border px-3 py-2 rounded"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>

        {loginError && (
          <pre className="text-red-600 bg-red-50 p-2 rounded">
            {JSON.stringify(loginError, null, 2)}
          </pre>
        )}


        <button
          type="submit"
          className="w-full bg-blue-600 text-white py-2 rounded disabled:opacity-50"
          disabled={loading}
        >
          {loading ? 'Logging in...' : 'Login'}
        </button>
      </form>

      <div className="mt-6">
        <div className="relative">
          <div className="absolute inset-0 flex items-center">
            <div className="w-full border-t border-gray-300"></div>
          </div>
          <div className="relative flex justify-center text-sm">
            <span className="px-2 bg-white text-gray-500">Or continue with</span>
          </div>
        </div>

        <div className="mt-6 space-y-3">
          <button
            onClick={handleGoogleLogin}
            type="button"
            className="w-full inline-flex justify-center items-center gap-2 border border-gray-300 rounded-md px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
            disabled={loading}
          >
            <svg className="w-5 h-5" viewBox="0 0 24 24" aria-hidden="true">
              {/* Google SVG Path */}
              <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
              <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
              <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
              <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
            </svg>
            Continue with Google
          </button>

          <button
            onClick={handleFacebookLogin}
            type="button"
            className="w-full inline-flex justify-center items-center gap-2 border border-gray-300 rounded-md px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
            disabled={loading}
          >
            <svg className="w-5 h-5 text-blue-600" viewBox="0 0 24 24" fill="currentColor">
              <path d="M22.675 0h-21.35C.592 0 0 .592 0 1.325v21.351C0 23.407.592 24 1.325 24H12.82v-9.294H9.692v-3.622h3.127V8.413c0-3.1 1.894-4.788 4.659-4.788 1.325 0 2.464.099 2.794.143v3.24l-1.918.001c-1.504 0-1.796.715-1.796 1.763v2.31h3.587l-.467 3.622h-3.12V24h6.116C23.407 24 24 23.407 24 22.676V1.325C24 .592 23.407 0 22.675 0z" />
            </svg>
            Continue with Facebook
          </button>

          <button
            onClick={() => socialLogin('linkedin')}
            type="button"
            className="w-full inline-flex justify-center items-center gap-2 border border-gray-300 rounded-md px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
            disabled={loading}
          >
            <svg className="w-5 h-5 text-blue-700" viewBox="0 0 24 24" fill="currentColor">
              <path d="M4.98 3.5C4.98 4.88 3.88 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1 4.98 2.12 4.98 3.5zM.5 8h4v14h-4V8zm7.5 0h3.8v2h.05c.53-1 1.83-2 3.75-2 4 0 4.7 2.5 4.7 5.7V22h-4v-7.3c0-1.7-.03-3.9-2.4-3.9s-2.75 1.9-2.75 3.8V22h-4V8z" />
            </svg>
            Continue with LinkedIn
          </button>

        </div>
      </div>
    </div>
  );
};

export default LoginPage;

Hooks and Events

These optional callbacks are available via AuthProvider:

<AuthProvider
  onLoginSuccess={(user) => console.log('Welcome', user)}
  onLoginFail={(error) => console.warn('Login error', error)}
  onLogout={() => console.log('Logged out')}
/>

API Reference

useAuth() Returns the following:

{
  user: User | null;
  isAuthenticated: boolean;
  loading: boolean;
  login: (payload: LoginPayload) => Promise<void>;
  logout: () => void;
  socialLogin: (provider: SocialProvider) => void;
  loginError: Record<string, unknown> | null;
}

login(payload)

login({
  email: '[email protected]',
  password: '123456',
  provider: 'credentials'
});

socialLogin(provider)

socialLogin('google'); // or 'facebook', 'linkedin'

Types

You can import types like:

import type { LoginPayload, User } from '@inertiapixel/nextjs-auth';

Backend Package Information

After setting up the frontend package, you can install the companion backend package in your Node js project to complete the full authentication workflow.

Backend Auth package


License

MIT © inertiapixel


Related Projects

Crafted in India by InertiaPixel 🇮🇳