New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@drop-in/pass

Package Overview
Dependencies
Maintainers
1
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@drop-in/pass

Your drop-in season pass. aka Auth

latest
npmnpm
Version
0.5.0
Version published
Maintainers
1
Created
Source

@drop-in/pass

Your drop-in season pass. aka Auth

A secure, modern authentication library for SvelteKit applications with HttpOnly JWT cookies, refresh token rotation, and comprehensive session management. Runtime agnostic - works in Node.js, Cloudflare Workers, Deno, Bun, and other environments.

✨ Features

  • 🔒 Secure by default - HttpOnly cookies, CSRF protection, bcrypt password hashing
  • 🔄 Automatic token refresh - Transparent JWT renewal with refresh token rotation
  • 📧 Email verification - Built-in email verification workflow with flexible provider configuration
  • 🌐 Runtime agnostic - Works in Node.js, Cloudflare Workers, Deno, Bun, and other environments
  • 🏗️ SvelteKit optimized - Native hooks integration and SSR support
  • 📊 Session management - Server-side user context and authentication state
  • 🧪 Well tested - 86+ tests covering all core functionality
  • 📝 TypeScript first - Full type safety throughout

🚀 Quick Start

Installation

npm install @drop-in/pass

Database Setup

Database is provided via dependency injection only.

  • Create a Drizzle instance in your app.
  • Pass it to our SvelteKit handle factories.
# .env
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
JWT_SECRET="your-secret-key-here"

Database is provided via dependency injection only; pass your Drizzle instance to create_session_handle(db) and create_pass_routes(db) as shown below.

Injecting your Drizzle instance

Use our factories in hooks.server.ts (or equivalent) to inject your Drizzle instance before requests hit auth routes.

Example with Node Postgres Pool (Node runtimes):

// src/hooks.server.ts (or your server init)
import { create_pass_routes, create_session_handle } from '@drop-in/pass';
import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import * as schema from '@drop-in/pass/schema';
import { sequence } from '@sveltejs/kit/hooks';

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const drizzleDb = drizzle(pool, { schema });

export const handle = sequence(
  create_session_handle(drizzleDb),
  create_pass_routes(drizzleDb)
);

Example with Cloudflare Hyperdrive (Workers):

// src/hooks.server.ts (Cloudflare Workers)
import { create_pass_routes, create_session_handle } from '@drop-in/pass';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from '@drop-in/pass/schema';
import { sequence } from '@sveltejs/kit/hooks';

export const handle: Handle = async ({ event, resolve }) => {
  const env = event.platform?.env as any;
  const sql = postgres(env.DATABASE_URL, { prepare: true }); // via Hyperdrive
  const db = drizzle(sql, { schema });
  const chain = sequence(
    create_session_handle(db),
    create_pass_routes(db)
  );
  return chain({ event, resolve });
};

Example with Neon (serverless):

// src/hooks.server.ts (Neon serverless)
import { create_pass_routes, create_session_handle } from '@drop-in/pass';
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from '@drop-in/pass/schema';
import { sequence } from '@sveltejs/kit/hooks';

const sql = neon(process.env.DATABASE_URL!);
const db = drizzle(sql, { schema });

export const handle = sequence(
  create_session_handle(db),
  create_pass_routes(db)
);

Notes:

  • Instantiate Drizzle per process/request-lifetime depending on your runtime model.
  • In development, you can enable debug logs with DEBUG or NODE_ENV !== 'production'.
  • All server APIs accept a db instance via factory functions; you control how and where Drizzle is instantiated.

Email Configuration

Password reset links are generated using create_password_link(email) and include query params: email, key (token), and expire (timestamp). The default expiration is 24 hours. The reset endpoint expects these parameters.

Configure your email provider in drop-in.config.js:

// For Cloudflare Workers with Resend
const sendEmail = async ({ to, subject, html, from }) => {
  const response = await fetch('https://api.resend.com/emails', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.RESEND_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ to, subject, html, from }),
  });
  
  if (!response.ok) {
    throw new Error(`Failed to send email: ${response.statusText}`);
  }
};

export default {
  email: {
    from: 'noreply@yourdomain.com',
    sendEmail,
  },
  app: {
    url: 'https://yourdomain.com',
    name: 'Your App',
    route: '/dashboard'
  }
};

Supported email providers:

  • Resend - Modern email API, perfect for Cloudflare Workers
  • MailChannels - Free email sending for Cloudflare Workers
  • SendGrid - Reliable email delivery service
  • SMTP - Traditional email with @drop-in/beeper for Node.js

See Email Configuration Guide for detailed examples.

Basic Setup

Note: Signing up (POST /api/auth/register) automatically triggers a verification email in the background. The response is not delayed by email sending; failures are logged and do not block signup.

  • Configure your hooks (src/hooks.server.ts):
import { create_pass_routes, create_session_handle } from '@drop-in/pass';
import { sequence } from '@sveltejs/kit/hooks';
import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import * as schema from '@drop-in/pass/schema';

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool, { schema });

export const handle = sequence(
  create_session_handle(db),  // Populates event.locals.user automatically
  create_pass_routes(db)      // Handles auth routes (/api/auth/*)
);
  • Configure global settings (drop-in.config.js):
export default {
  email: {
    from: 'noreply@yourdomain.com',
    sendEmail: yourEmailFunction, // Your email implementation
  },
  app: {
    url: 'https://yourdomain.com',
    name: 'Your App Name',
    route: '/dashboard'
  }
};

Usage

Client-Side Authentication

import { pass } from '@drop-in/pass/client';

// Sign up
try {
  const result = await pass.signup('user@example.com', 'securepassword');
  console.log('Signed up successfully!', result.user);
} catch (error) {
  console.error('Signup failed:', error.message);
}

// Login
try {
  const result = await pass.login('user@example.com', 'securepassword');
  console.log('Logged in successfully!', result.user);
} catch (error) {
  console.error('Login failed:', error.message);
}

// Get current user
try {
  const { user } = await pass.me();
  console.log('Current user:', user);
} catch (error) {
  console.log('Not authenticated');
}

// Logout
await pass.logout();

Server-Side Usage

// In load functions, API routes, or hooks
export async function load({ locals }) {
  if (locals.user) {
    console.log('User is authenticated:', locals.user.id);
    return {
      user: locals.user
    };
  }
  
  // User is not authenticated
  return {};
}
// Manual authentication in API routes
import { authenticate_user } from '@drop-in/pass';

export async function GET({ cookies }) {
  const auth = await authenticate_user(db, cookies);
  
  if (!auth) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  // User is authenticated
  console.log('User ID:', auth.user_id);
  return new Response('Hello authenticated user!');
}

🛡️ Security Features

HttpOnly Cookies

  • Access tokens are HttpOnly, secure, and long-lived (90 days)
  • Refresh tokens are HttpOnly, secure, and long-lived (90 days)
  • SameSite=strict protection against CSRF attacks
  • Automatic token refresh happens transparently

Password Security

  • bcrypt hashing with salt rounds (configurable, default: 10)
  • Backward compatibility for password hash migration
  • Minimum password requirements (6+ characters, configurable)

Session Management

  • Database-stored refresh tokens with automatic cleanup
  • Token rotation on each refresh
  • Secure logout that invalidates all tokens
  • Protection against token reuse

📖 API Reference

Client API (@drop-in/pass/client)

pass.signup(email: string, password: string)

Creates a new user account.

Returns: Promise<{ user: User }>

Throws: Error with validation or server error messages

pass.login(email: string, password: string)

Authenticates a user.

Returns: Promise<{ user: User }>

Throws: Error with authentication failure details

pass.logout()

Logs out the current user.

Returns: Promise<Response>

pass.requestPasswordReset(email: string)

Requests a password reset email. Always returns success to avoid user enumeration.

Returns: Promise<Response>

pass.resetPassword(email: string, token: string, expire: number, password: string)

Completes password reset. On success, sets HttpOnly cookies for JWT and refresh token.

Returns: Promise<Response>

pass.me()

Gets current authenticated user information.

Returns: Promise<{ user: User }>

Throws: Error if not authenticated

Server API

authenticate_user(db: DrizzleDb, cookies: Cookies)

Manually authenticate a user from cookies.

const auth = await authenticate_user(db, cookies);
if (auth) {
  console.log('User ID:', auth.user_id);
}

populate_user_session(db: DrizzleDb, event: RequestEvent)

Manually populate event.locals.user with authenticated user data.

await populate_user_session(db, event);
console.log(event.locals.user); // User object or undefined

Built-in Routes

The library automatically handles these routes when using create_pass_routes(db):

  • POST /api/auth/login - User login
  • POST /api/auth/register - User registration (auto-sends verification email; non-blocking)
  • POST /api/auth/logout - User logout
  • GET /api/auth/me - Get current user
  • POST /api/auth/verify-email - Email verification
  • POST /api/auth/send-verify-email - Send verification email
  • POST /api/auth/forgot-password - Request password reset (always returns success)
  • POST /api/auth/reset-password - Complete password reset and sign in

🔧 Configuration

// Refresh token settings (src/cookies.ts)
export const cookie_options = {
  httpOnly: true,
  secure: true,
  path: '/',
  sameSite: 'strict' as const,
  maxAge: 60 * 60 * 24 * 90, // 90 days
};

// JWT settings
export const jwt_cookie_options = {
  path: '/',
  maxAge: 60 * 60 * 24 * 90, // 90 days
  httpOnly: true,
  sameSite: 'strict' as const,
  secure: true,
};

Environment Variables

# Required
DATABASE_URL="postgresql://..."
JWT_SECRET="your-jwt-secret"

# Optional email API keys (choose one based on your provider)
RESEND_API_KEY="re_your_api_key"           # For Resend
SENDGRID_API_KEY="SG.your_api_key"         # For SendGrid
# MailChannels requires no API key for Cloudflare Workers

# Legacy SMTP settings (if using @drop-in/beeper)
EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT="587"
EMAIL_SECURE="true"
EMAIL_USER="your-email@gmail.com"
EMAIL_PASSWORD="your-app-password"

🧪 Testing

The library includes comprehensive test coverage:

npm test  # Run all tests
npm run test:watch  # Watch mode

Test Coverage:

  • ✅ Password hashing and verification
  • ✅ JWT creation and validation
  • ✅ Token refresh flow
  • ✅ Authentication middleware
  • ✅ Login/signup flows
  • ✅ Utility functions
  • ✅ Client API calls

🔄 Migration from Non-HttpOnly Setup

If you're upgrading from a version that used readable JWTs:

  • Update hooks.server.ts to include create_session_handle(db)
  • Replace client-side JWT reading with server-side locals.user or pass.me()
  • Remove manual cookie handling - all cookie management is now automatic

See SECURITY-UPGRADE.md for detailed migration instructions.

🤝 TypeScript Support

Full TypeScript support with type definitions for:

import type { User } from '@drop-in/pass/schema';

// Event locals typing is automatic
declare global {
  namespace App {
    interface Locals {
      user?: Partial<User>;
    }
  }
}

🚨 Security Considerations

  • Always use HTTPS in production - cookies won't work properly over HTTP
  • Set secure environment variables - never commit secrets to version control
  • Configure CSP headers - additional XSS protection
  • Monitor for suspicious activity - implement rate limiting for auth endpoints
  • Regular security updates - keep dependencies updated

📋 Development

# Install dependencies
npm install

# Run tests
npm test

# Build the package
npm run build

# Development mode
npm run dev

🐛 Troubleshooting

Common Issues

"Not authenticated" errors in production

  • Ensure HTTPS is properly configured
  • Check that cookies are being set with correct domain
  • Verify SameSite settings for your deployment

Database connection errors

  • Verify DATABASE_URL environment variable
  • Ensure PostgreSQL is running and accessible
  • Check database schema is properly set up

Email verification not working or no email received

  • Configure email provider in drop-in.config.js with your sendEmail callback (signup triggers verification automatically)
  • Set up email service credentials (API keys) in environment variables
  • Check spam/junk folders
  • Verify your email provider configuration is correct

Runtime compatibility issues

  • Ensure your email implementation uses only Web APIs (fetch, etc.) for Cloudflare Workers
  • For Node.js SMTP, use @drop-in/beeper as your email callback
  • Avoid Node.js-specific modules in Cloudflare Workers environments

Debug Mode

Enable debug logging:

DEBUG=drop-in:* npm run dev

🤝 Contributing

We welcome contributions! Please see our contributing guidelines and:

  • Add tests for any new features
  • Update documentation for API changes
  • Follow TypeScript best practices
  • Ensure security review for auth-related changes

📄 License

ISC License - see LICENSE file for details.

🙏 Acknowledgments

Built with:

Made with ❤️ for the SvelteKit community

FAQs

Package last updated on 29 Sep 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts