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

@main12/auth-login

v0.3.1

Published

Reusable Payload CMS auth plugin — login, signup, OTP, forgot password, branded emails, Powered by Main12

Readme

@main12/auth-login

Payload CMS authentication plugin — login, signup, OTP, forgot password, branded emails, and "Powered by Main 12" footer. Install once, done.

// payload.config.ts
import { authLoginPlugin } from '@main12/auth-login'

plugins: [
  authLoginPlugin({
    projectName: 'My SaaS',
    domain: 'https://myapp.com',
    logo: 'https://myapp.com/logo.png',
    style: 'tailwind',
    routeRedirects: true,
  }),
]

Features

  • 5 auth pages — login (multi-step), signup, forgot password, verify OTP, set password
  • Single catch-all route — one file handles all auth pages (AuthPages component)
  • Route redirects — automatic /login/auth/login, /admin/login/auth/login via Next.js 16 proxy
  • Multi-styletailwind (zero UI deps) or hero-ui (HeroUI + framer-motion). Set once in config
  • Google OAuth — optional, enable via providers config (hidden by default)
  • 5 API endpointscheck-email, otp/send, otp/verify, set-password, signup
  • OTP engine — SHA-256 hashing + timingSafeEqual comparison, 10-min expiry, 3 attempts
  • Email templates — welcome, OTP login, password reset, password changed (EN/ES)
  • Powered by Main 12 — bundled inline SVG, linked to main12.com by default (URL overridable)
  • Global logo — pass once in plugin config, automatically shown on all pages and email headers
  • Zero runtime deps (Tailwind mode) — Next.js + React are peer dependencies

Installation

pnpm add @main12/auth-login

Tailwind mode (default, zero UI deps — also ShadCN compatible)

No extra dependencies needed. The Tailwind style uses standard utility classes that work in any Tailwind project, including ShadCN-based ones.

HeroUI mode

pnpm add @heroui/react framer-motion @iconify/react

Quick Start (Simplified Setup)

The fastest way to get all auth pages running with just 2 files.

1. Add the plugin + Users collection

// payload.config.ts
import { authLoginPlugin } from '@main12/auth-login'

export default buildConfig({
  collections: [
    {
      slug: 'users',
      auth: { tokenExpiration: 7200, verify: false, maxLoginAttempts: 5 },
      fields: [
        { name: 'name', type: 'text' },
        { name: 'otpHash', type: 'text', admin: { hidden: true } },
        { name: 'otpAttempts', type: 'number', admin: { hidden: true } },
        { name: 'otpExpiresAt', type: 'text', admin: { hidden: true } },
      ],
    },
  ],
  plugins: [
    authLoginPlugin({
      projectName: 'My App',
      domain: 'https://myapp.com',
      logo: '/logo.png',
      style: 'tailwind',
      routeRedirects: true,   // enables /login → /auth/login redirects
    }),
  ],
})

2. Create the catch-all auth route (1 file)

// src/app/(frontend)/(auth)/auth/[...slug]/page.tsx
'use client'

import { use } from 'react'
import { AuthPages } from '@main12/auth-login/client'

export default function Page({ params }: { params: Promise<{ slug: string[] }> }) {
  const { slug } = use(params)
  return <AuthPages slug={slug} />
}

This single file handles all routes:

  • /auth/login
  • /auth/signup
  • /auth/forgot-password
  • /auth/verify-otp
  • /auth/set-password

3. Add the proxy for route redirects (1 file)

// src/proxy.ts (Next.js 16+)
export { proxy, config } from '@main12/auth-login/proxy'

This redirects:

  • /login/auth/login
  • /signup/auth/signup
  • /forgot-password/auth/forgot-password
  • /verify-otp/auth/verify-otp
  • /set-password/auth/set-password
  • /admin/login/auth/login (always, regardless of routeRedirects setting)

4. Visit /login — done.


Configuration

authLoginPlugin({
  // Core
  projectName: 'My App',          // Email subjects + footers
  contactEmail: '[email protected]',   // Email footer contact
  domain: 'https://myapp.com',    // Links in emails
  logo: '/logo.png',              // All auth pages + email headers
  style: 'tailwind',              // 'tailwind' | 'hero-ui'
  enabled: true,

  // Route redirects
  routeRedirects: true,           // Enable /login → /auth/login redirects
  // or with custom base path:
  // routeRedirects: { basePath: '/auth' },

  // OAuth providers (optional — hidden by default)
  providers: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    },
  },
})

Route Redirects

| Value | Behavior | |-------|----------| | false (default) | No redirects. You manage your own routes. | | true | Redirects /login, /signup, etc. → /auth/login, /auth/signup, etc. | | { basePath: '/myauth' } | Same, but redirects to /myauth/login, etc. |

Note: /admin/login is always redirected to the plugin login page when the proxy is active, regardless of the routeRedirects setting.

Google OAuth (Providers)

Google OAuth is hidden by default. To enable it, just add providers.google — the plugin handles everything (UI buttons + server-side OAuth flow via payload-oauth2 under the hood):

authLoginPlugin({
  providers: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      successRedirect: '/dashboard',    // optional, defaults to '/admin'
      failureRedirect: '/login?error=failed', // optional
    },
  },
})

That's it. No extra packages to install, no extra plugins to configure.

| Config | Behavior | |--------|----------| | Omitted / not set | Google OAuth hidden | | google: true | Auto-detect GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET from env | | google: { clientId, clientSecret } | Enable with explicit credentials | | google: false | Force disable |


AuthPages Props

The AuthPages component accepts these props for customization:

<AuthPages
  slug={slug}                    // Required — from catch-all route params
  redirectTo="/dashboard"        // Where to go after login/set-password (default: '/admin')
  logo={<MyLogo />}             // Custom logo component
  basePath="/auth"               // Base path for sibling links (default: '/auth')
  showGoogleOAuth={true}         // Override Google OAuth visibility
  onPasswordLogin={customLogin}  // Custom login handler
  onSignup={customSignup}        // Custom signup handler
/>

Individual Page Setup (Advanced)

If you need full control over each page, create separate route files instead of using AuthPages:

// login/page.tsx
'use client'
import { LoginPage } from '@main12/auth-login/client'

export default function Page() {
  return (
    <LoginPage
      onPasswordLogin={async ({ email, password }) => {
        const res = await fetch('/api/users/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email, password }),
        })
        if (!res.ok) throw new Error('Login failed')
      }}
      redirectTo="/dashboard"
      signupUrl="/signup"
    />
  )
}

Per-page overrides

<LoginPage
  logo={<AppLogo width={180} />}
  onPasswordLogin={login}
  redirectTo="/dashboard"
  showGoogleOAuth={true}
  signupUrl="/signup"
/>

<SignupPage onSignup={signup} loginUrl="/login" />
<ForgotPasswordPage loginUrl="/login" />
<VerifyOtpPage loginUrl="/login" />
<SetPasswordPage redirectTo="/dashboard" />

Building Custom Pages

Use the plugin's hooks to build your own UI with any component library (HeroUI, shadcn, plain Tailwind).

Hook Quick-Reference

| Hook | Returns | Key inputs | |------|---------|------------| | useLoginFlow({ redirectTo, onPasswordLogin }) | step, email, password, error, isLoading, handleEmailSubmit, handlePasswordSubmit, handleSendOtp, handleEditEmail | redirectTo: string, onPasswordLogin: (creds) => Promise<void> | | useForgotPasswordFlow() | email, error, isLoading, setEmail, handleSubmit | none | | useVerifyOtpFlow({ email, purpose, redirectTo }) | otp, error, isLoading, isResending, resendCooldown, setOtp, handleSubmit, handleResendCode | email: string, purpose: 'login'\|'signup'\|'password-reset' | | useSetPasswordFlow({ redirectTo }) | password, confirmPassword, error, isLoading, strength, setPassword, setConfirmPassword, handleSubmit | redirectTo: string |

Signup note: No hook needed — call signup(name, email) from @main12/auth-login/client, then redirect to /verify-otp?email=...&purpose=signup.

Full Example: Custom Login Page

'use client'
import { useLoginFlow } from '@main12/auth-login/client'
import { useAuth } from '@/providers/Auth'

export default function CustomLogin() {
  const { login } = useAuth()
  const {
    step, email, password, error, isLoading, showPassword,
    setEmail, setPassword, setShowPassword,
    handleEmailSubmit, handlePasswordSubmit, handleSendOtp, handleEditEmail,
  } = useLoginFlow({ redirectTo: '/dashboard', onPasswordLogin: login })

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <div className="w-full max-w-md p-8 bg-white rounded-2xl shadow-xl">
        <img src="/logo.png" className="mx-auto mb-6" width={180} alt="" />

        {/* Step 1: Email */}
        {step === 'email' && (
          <form onSubmit={handleEmailSubmit} className="space-y-4">
            <h1 className="text-xl font-semibold text-center">Welcome Back</h1>
            {error && <p className="text-red-600 text-sm">{error}</p>}
            <input type="email" value={email} onChange={e => setEmail(e.target.value)}
              placeholder="Email" required className="w-full h-12 px-4 border rounded-xl" />
            <button type="submit" disabled={isLoading}
              className="w-full h-12 bg-[#D5E855] rounded-full font-semibold">
              {isLoading ? 'Loading...' : 'Continue'}
            </button>
          </form>
        )}

        {/* Step 2a: Password */}
        {step === 'password' && (
          <form onSubmit={handlePasswordSubmit} className="space-y-4">
            <div className="flex justify-between border rounded-xl px-4 py-3">
              <span>{email}</span>
              <button type="button" onClick={handleEditEmail} className="text-sm">Edit</button>
            </div>
            {error && <p className="text-red-600 text-sm">{error}</p>}
            <input type={showPassword ? 'text' : 'password'} value={password}
              onChange={e => setPassword(e.target.value)} placeholder="Password" required
              className="w-full h-12 px-4 border rounded-xl" autoFocus />
            <button type="submit" disabled={isLoading}
              className="w-full h-12 bg-[#D5E855] rounded-full font-semibold">
              {isLoading ? 'Loading...' : 'Sign In'}
            </button>
          </form>
        )}

        {/* Step 2b: OTP Prompt (migrated users without password) */}
        {step === 'otp-prompt' && (
          <div className="space-y-4">
            <div className="flex justify-between border rounded-xl px-4 py-3">
              <span>{email}</span>
              <button type="button" onClick={handleEditEmail}>Edit</button>
            </div>
            {error && <p className="text-red-600 text-sm">{error}</p>}
            <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-sm text-blue-700">
              We'll send a verification code to this email.
            </div>
            <button onClick={handleSendOtp} disabled={isLoading}
              className="w-full h-12 bg-[#D5E855] rounded-full font-semibold">
              {isLoading ? 'Sending...' : 'Send Code'}
            </button>
          </div>
        )}
      </div>
    </div>
  )
}

Custom Signup

Same pattern — call onSignup(name, email) from your form, redirect to /verify-otp?email=...&purpose=signup on success.

Custom Verify OTP

Use useVerifyOtpFlow({ email, purpose, redirectTo }). It manages the 6-digit input, verification, resend cooldown (60s), and auto-redirect. All you need is an <input> or <InputOtp> for the code and a verify button.

Custom Forgot Password

Use useForgotPasswordFlow(). Collect email, call handleSubmit(e). It checks user exists, sends OTP, and redirects to /verify-otp?email=...&purpose=password-reset.

Custom Set Password

Use useSetPasswordFlow({ redirectTo }). Two password fields (new + confirm). The hook validates strength (≥8 chars, 3/4 criteria) and matches. Returns strength.score (0-5) for a visual indicator.


Custom Email Templates

import { generateWelcomeEmail, getEmailTranslations } from '@main12/auth-login/rsc'

// Override translations
const t = getEmailTranslations('en')
t.welcome.subject = 'Welcome to My SaaS! 🚀'

// Or wrap template generators
function myWelcome(params) {
  const base = generateWelcomeEmail(params)
  return { ...base, html: base.html.replace('Get Started', 'Launch Now') }
}

// Use in hooks/endpoints
await payload.sendEmail({
  to: user.email,
  ...myWelcome({ userName: user.name, userEmail: user.email }),
})

API Endpoints

| Method | Path | Description | |--------|------|-------------| | POST | /api/auth/check-email | Check if email is registered | | POST | /api/auth/otp/send | Generate + send OTP via Payload email adapter | | POST | /api/auth/otp/verify | Verify OTP + login (sets httpOnly cookie) | | POST | /api/auth/set-password | Set/update password | | POST | /api/auth/signup | Create account + send welcome email |


Exports

| Import path | Contents | |-------------|----------| | @main12/auth-login | Plugin factory, types, server config | | @main12/auth-login/client | Page components, AuthPages, hooks, services, UI utilities | | @main12/auth-login/rsc | Email template generators, translations | | @main12/auth-login/proxy | Next.js 16 proxy for route redirects |


ShadCN Compatibility

The tailwind style works in ShadCN projects out of the box. For ShadCN components, build a custom page — import the plugin's hooks and use your @/components/ui/button, @/components/ui/input, etc.


Setup Comparison

| Approach | Files needed | Best for | |----------|-------------|----------| | Catch-all + proxy (recommended) | 2 files | Most projects — fastest setup | | Individual pages | 5 files | Full control over each page | | Custom pages with hooks | Your own files | Completely custom UI | | Backend only (routeRedirects: false, no AuthPages) | 0 frontend files | Custom frontend using only the API endpoints + hooks |


🤖 AI Agent Prompts

Copy these prompts into Claude, Cursor, Copilot, or any AI agent.

Prompt: Set up the auth plugin (simplified catch-all)

Add @main12/auth-login to this Payload project:

1. Install: pnpm add @main12/auth-login
2. In payload.config.ts, add:
   - Users collection with auth enabled + otpHash, otpAttempts, otpExpiresAt fields
   - Plugin: authLoginPlugin({ projectName: "<PROJECT>", domain: "<URL>", logo: "/logo.png", style: "tailwind", routeRedirects: true })
3. Create catch-all route: src/app/(frontend)/(auth)/auth/[...slug]/page.tsx
   - 'use client', import { use } from 'react', import { AuthPages } from '@main12/auth-login/client'
   - export default function Page({ params }) { const { slug } = use(params); return <AuthPages slug={slug} /> }
4. Create proxy: src/proxy.ts
   - export { proxy, config } from '@main12/auth-login/proxy'
5. Verify: visit /login → should redirect to /auth/login

Prompt: Set up with Google OAuth

Add @main12/auth-login with Google OAuth to this Payload project:

1. Install: pnpm add @main12/auth-login
2. In payload.config.ts, add:
   - Users collection with auth enabled + otpHash, otpAttempts, otpExpiresAt fields
   - Plugin: authLoginPlugin({
       projectName: "<PROJECT>",
       domain: "<URL>",
       style: "tailwind",
       routeRedirects: true,
       providers: {
         google: {
           clientId: process.env.GOOGLE_CLIENT_ID,
           clientSecret: process.env.GOOGLE_CLIENT_SECRET,
         }
       }
     })
3. Create catch-all route + proxy (same as simplified setup)
4. Add GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to .env
5. Verify: visit /login → should show Google OAuth button

Prompt: Build a custom login page with HeroUI components

Build a custom login page using @main12/auth-login hooks and @heroui/react:

- Import useLoginFlow from '@main12/auth-login/client'
- Use useAuth() from Payload's Auth provider for the login function
- Render 3 steps: email input → password input / OTP prompt
- Use HeroUI <Button>, <Input> with variant="bordered", rounded-full
- Add "Continue with Google" button using initiateGoogleLogin from the plugin
- Add "Powered by Main12" footer from the plugin's PoweredBy component

Prompt: Customize email templates for my project

Customize the email templates from @main12/auth-login for my project "<PROJECT_NAME>":

- Import generateWelcomeEmail, generateOtpEmail, generatePasswordResetEmail, generatePasswordChangedEmail from '@main12/auth-login/rsc'
- Override each to use my brand colors (primary: <COLOR>, accent: <COLOR>)
- Change the welcome email CTA text to "<CUSTOM_TEXT>"
- Change the OTP email purpose text to "<CUSTOM_TEXT>"
- Add my project's social media links to the footer

Dev Testing

git clone https://github.com/MAIN-12/auth-login-plugin.git
cd auth-login-plugin
pnpm install
pnpm dev

Visit http://localhost:3000/login — all 5 auth pages wired with SQLite.


Usage with @main12/brevo-adapter

import { authLoginPlugin } from '@main12/auth-login'
import { brevoAdapter } from '@main12/brevo-adapter'

export default buildConfig({
  email: brevoAdapter(),
  plugins: [authLoginPlugin({ projectName: 'My App' })],
})

The auth-login plugin uses payload.sendEmail() internally — which routes through Brevo automatically.

Requirements

| Dependency | Version | Required | |------------|---------|----------| | Payload CMS | ^3.82.0 | ✅ | | Next.js | ^16.0.0 | ✅ | | React | ^19.0.0 | ✅ | | HeroUI | ^2.x | Only for style: 'hero-ui' | | Framer Motion | ^12.x | Only for style: 'hero-ui' |


License

MIT © Main 12