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

swift-auth

v1.6.2

Published

Custom authentication system for apps

Readme

⚡ Swift Auth

Type-safe, zero-hassle authentication for Next.js

npm version License Node TypeScript Next.js

Lightweight • Secure • Type-Safe • Production-Ready

Quick StartAPI ReferenceExamplesTroubleshooting


🌟 Why Swift Auth?

Stop wrestling with authentication boilerplate. Swift Auth handles session management, password security, and cookie handling—so you can focus on building amazing features.

Built for Next.js 15/16 with Upstash Redis, optimized for type safety and security.


✨ Key Features

| Feature | Description | | ------------------------ | ---------------------------------------------------- | | 🔐 Type-Safe | Full TypeScript support with generic user types | | 🛡️ Secure by Default | Password hashing via scrypt + timing-safe comparison | | 🍪 Smart Cookies | Automatic HTTP-only cookies (dev & production) | | ⚡ Next.js 15+ | Works seamlessly with async cookies() API | | 📦 Minimal Footprint | You control what's stored in Redis | | 🚀 Zero Crypto Deps | No external dependencies for hashing | | ✅ Production-Ready | Battle-tested session management | | 🔄 Flexible Hashing | Use built-in scrypt or bring your own encryption |


📦 Installation

Install Swift Auth using your favorite package manager:

npm install swift-auth

Or with yarn/pnpm:

yarn add swift-auth
# or
pnpm add swift-auth

🎯 Quick Start (5 minutes)

Step 1️⃣ Prerequisites

Make sure you have:

  • Node.js 18+
  • Next.js 15 or 16
  • PostgreSQL (via Prisma)
  • Upstash Redis account

Step 2️⃣ Environment Variables

Create a .env.local file:

DATABASE_URL=postgresql://user:password@localhost:5432/mydb
REDIS_URL=https://your-instance.upstash.io
REDIS_TOKEN=your_auth_token

Step 3️⃣ Database Setup & Password Hashing

Choose your password hashing approach:

Option A: Use Swift Auth's Built-in Hashing (Recommended)

Configure your Prisma schema with the user model including salt:

// prisma/schema.prisma

generator client {
  provider = "prisma-client"
  output   = "../lib/generated/prisma"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model user {
  id         String   @id @default(uuid())
  name       String
  email      String   @unique
  password   String
  salt       String   // Required for built-in hashing
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt
}

⚠️ Critical: Salt must be stored as a String, not Buffer or Bytes.

Option B: Use Your Own Encryption Method

If you prefer your own password hashing logic, omit the salt field:

model user {
  id         String   @id @default(uuid())
  name       String
  email      String   @unique
  password   String   // Your pre-encrypted password
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt
}

🔒 Password Management

Option A: Swift Auth Built-in Hashing

Generate Salt & Hash Password

const salt = auth.generateSalt();
const hashedPassword = await auth.hashPassword("user-password", salt);

// Store both hashedPassword and salt as STRINGS in your database
await prisma.user.create({
  data: {
    email: "[email protected]",
    password: hashedPassword,
    salt: salt,
    name: "John Doe",
  },
});

Verify Password During Login

const isValid = await auth.comparePassword({
  password: "user-password",
  salt,
  hashedPassword,
});

if (!isValid) {
  return { success: false, message: "Invalid password" };
}

Option B: Custom Encryption

Use your own encryption method before storing in the database:

import bcrypt from "bcrypt"; // or any other method

// During registration
const hashedPassword = await bcrypt.hash("user-password", 10);

await prisma.user.create({
  data: {
    email: "[email protected]",
    password: hashedPassword,
    name: "John Doe",
  },
});

// During login verification
const isValid = await bcrypt.compare("user-password", user.password);

Step 4️⃣ Create Auth Instance

// lib/auth.ts

import { createAuth } from "swift-auth";

export type User = {
  id: string;
  name: string;
  email: string;
  created_at: Date;
};

export const auth = createAuth<User>({
  redis: {
    url: process.env.REDIS_URL!,
    token: process.env.REDIS_TOKEN!,
  },
  ttl: 60 * 60 * 24 * 7, // 7 days
  payload: ["id", "name", "email", "created_at"],
});

Configuration Options:

| Option | Type | Description | | ------------- | ---------- | ------------------------- | | redis.url | string | Upstash Redis URL | | redis.token | string | Upstash Redis token | | ttl | number | Session TTL in seconds | | payload | string[] | Fields persisted in Redis |


🔐 Core Usage

Create Session (Login)

// app/actions/auth.ts
"use server";

import { auth } from "@/lib/auth";
import { cookies } from "next/headers";

export async function signIn(user: User) {
  const cookieStore = await cookies();
  await auth.createUserSession(user, cookieStore);
}

Get Current User

// app/dashboard/page.tsx
import { auth } from "@/lib/auth";
import { cookies } from "next/headers";

export default async function Dashboard() {
  const user = await auth.getCurrentUser(await cookies());

  if (!user) return <div>Please log in</div>;
  return <div>Welcome back, {user.name}! 👋</div>;
}

Update Session

// app/actions/auth.ts
"use server";

import { auth } from "@/lib/auth";
import { cookies } from "next/headers";

export async function updateProfile(user: User) {
  await auth.updateUserSession(user, await cookies());
}

Logout

// app/actions/auth.ts
"use server";

import { auth } from "@/lib/auth";
import { cookies } from "next/headers";

export async function signOut() {
  await auth.removeUserFromSession(await cookies());
}

📚 Full Login Example

Complete login flow with Prisma + Zod validation:

Validation Schema

// lib/validation.ts
import { z } from "zod";

export const signInSchema = z
  .object({
    email: z.string().email("Invalid email"),
    password: z.string().min(8, "Password too short"),
  })
  .strict();

export type SignInInput = z.infer<typeof signInSchema>;

Server Action

// app/actions/auth.ts
"use server";

import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { signInSchema } from "@/lib/validation";
import { cookies } from "next/headers";

export async function signIn(formData: unknown) {
  try {
    // 1. Validate input
    const parsed = signInSchema.safeParse(formData);
    if (!parsed.success) {
      return {
        success: false,
        message: "Validation error",
        errors: parsed.error.flatten(),
      };
    }

    const { email, password } = parsed.data;

    // 2. Find user in database
    const user = await prisma.user.findUnique({
      where: { email },
    });

    if (!user) {
      return { success: false, message: "Account not found" };
    }

    // 3. Verify password (choose your method)
    // Option A: Using Swift Auth's built-in method
    const isCorrectPassword = await auth.comparePassword({
      hashedPassword: user.password,
      password,
      salt: user.salt, // if using built-in hashing
    });

    // Option B: Using custom encryption (e.g., bcrypt)
    // const isCorrectPassword = await bcrypt.compare(password, user.password);

    if (!isCorrectPassword) {
      return { success: false, message: "Invalid password" };
    }

    // 4. Create session
    await auth.createUserSession(
      {
        id: user.id,
        name: user.name,
        email: user.email,
        created_at: user.created_at,
      },
      await cookies()
    );

    return {
      success: true,
      message: "Logged in successfully",
      userId: user.id,
    };
  } catch (error) {
    console.error("Auth Error:", error);
    return { success: false, message: "Internal server error" };
  }
}

🎯 API Reference

Session Management

| Method | Parameters | Returns | Description | | ------------------------- | --------------------- | --------------- | ------------------------------------ | | getCurrentUser() | cookieStore | User \| null | Get the currently authenticated user | | createUserSession() | user, cookieStore | Promise<void> | Create a new session | | updateUserSession() | user, cookieStore | Promise<void> | Update existing session | | removeUserFromSession() | cookieStore | Promise<void> | Logout user |

Password Management (Optional - Built-in only)

| Method | Parameters | Returns | Description | | ------------------- | ------------------------------------ | ------------------ | -------------------------------------- | | generateSalt() | - | string | Generate cryptographically secure salt | | hashPassword() | password, salt | Promise<string> | Hash password with scrypt | | comparePassword() | { password, salt, hashedPassword } | Promise<boolean> | Timing-safe password comparison |


🚀 Best Practices

Do:

  • Choose a hashing method before building your database schema
  • Store salt as a STRING if using Swift Auth's hashing
  • Use environment variables for Redis credentials
  • Call signOut() before navigating to login page
  • Update session after profile changes
  • Use TypeScript for type safety

Don't:

  • Mix hashing methods (pick one and stick with it)
  • Store salt as Buffer or Bytes (if using built-in hashing)
  • Hardcode Redis credentials
  • Compare passwords manually with ===
  • Expose session data to client components
  • Use TTL shorter than 1 hour for user experience

🐛 Troubleshooting

Redis Connection Failed

Problem: Error: Connection refused

Solution:

# Verify your Upstash instance is active
# Check REDIS_URL and REDIS_TOKEN in .env.local
echo $REDIS_URL

Session Not Found / Cookie Missing

Problem: User is logged out unexpectedly

Solution:

  • Check if TTL has expired
  • Verify payload includes all required user data
  • Ensure cookie store is being awaited properly
// ✅ Correct
const cookieStore = await cookies();

// ❌ Wrong
const cookieStore = cookies();

Type Errors with User Type

Problem: TypeScript complains about User type mismatch

Solution: Always export and reuse your User type:

// lib/auth.ts
export type User = {
  /* ... */
};

// app/actions/auth.ts
import type { User } from "@/lib/auth";

Password Comparison Always Fails (Built-in Hashing)

Problem: comparePassword() returns false for valid password

Solution: Ensure salt is retrieved as a STRING:

// ✅ Correct
const user = await prisma.user.findUnique({ where: { id } });
const isValid = await auth.comparePassword({
  password,
  salt: user.salt, // string ✓
  hashedPassword: user.password,
});

// ❌ Wrong
salt: user.salt as any; // don't cast!

📖 More Resources


📄 License

MIT © Taimoor Safdar


Built with ❤️ for the Next.js community

⬆ back to top