{"id":16921,"library":"velvet-auth","title":"velvet-auth","description":"velvet-auth is a production-ready authentication plugin specifically designed for Elysia.js applications running on Bun. It provides a comprehensive solution for common authentication patterns, including JWT rotation, secure password hashing using native Argon2id (via `Bun.password`), and session management with RESP-compatible stores like Redis for refresh token invalidation and JTI blacklisting. The current stable version is 0.1.9, with frequent minor releases addressing bug fixes and introducing improvements. Key differentiators include its tight integration with Bun's native features, an adapter pattern for database and email provider flexibility, and a focus on type safety with Zod validation. It aims to reduce boilerplate for setting up robust auth stacks in the Bun/Elysia ecosystem.","status":"active","version":"0.1.9","language":"javascript","source_language":"en","source_url":"https://github.com/raloonsoc/velvet-auth","tags":["javascript","elysia","bun","auth","authentication","jwt","redis","argon2","plugin","typescript"],"install":[{"cmd":"npm install velvet-auth","lang":"bash","label":"npm"},{"cmd":"yarn add velvet-auth","lang":"bash","label":"yarn"},{"cmd":"pnpm add velvet-auth","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Peer dependency, velvet-auth is an Elysia plugin.","package":"elysia","optional":false},{"reason":"Runtime dependency for native features like Bun.password and Bun.RedisClient.","package":"bun","optional":false},{"reason":"Runtime dependency for session management (refresh tokens, JTI blacklist), can be any RESP-compatible server like Valkey, KeyDB, Dragonfly, Garnet.","package":"redis","optional":false}],"imports":[{"note":"Main plugin export for Elysia. Primarily used with ESM. Types are included.","wrong":"const velvetAuth = require('velvet-auth')","symbol":"velvetAuth","correct":"import { velvetAuth } from 'velvet-auth'"},{"note":"Used to protect routes and inject `ctx.user`. Imported as a named export.","wrong":"import createAuthGuard from 'velvet-auth/guard'","symbol":"createAuthGuard","correct":"import { createAuthGuard } from 'velvet-auth'"},{"note":"TypeScript type definition for the database adapter interface. Should be imported as a type.","wrong":"import { UserStoreAdapter } from 'velvet-auth'","symbol":"UserStoreAdapter","correct":"import type { UserStoreAdapter } from 'velvet-auth'"}],"quickstart":{"code":"import { Elysia } from \"elysia\";\nimport { velvetAuth } from \"velvet-auth\";\nimport { BunFileRouter } from 'bun-file-router'; // Assuming a simple db mock for demonstration\n\nconst db = {\n  users: {\n    data: [] as any[], // In-memory mock for demonstration\n    findOne: async ({ id, username, email }: { id?: string; username?: string; email?: string }) => {\n      if (id) return db.users.data.find(u => u.id === id);\n      if (username) return db.users.data.find(u => u.username === username);\n      if (email) return db.users.data.find(u => u.email === email);\n      return undefined;\n    },\n    insert: async (data: any) => { db.users.data.push({ ...data, id: Date.now().toString() }); return data; },\n    update: async ({ id }: { id: string }, updates: any) => {\n      const userIndex = db.users.data.findIndex(u => u.id === id);\n      if (userIndex !== -1) {\n        db.users.data[userIndex] = { ...db.users.data[userIndex], ...updates };\n      }\n    },\n  },\n};\n\n// 1. Implement the UserStoreAdapter for your database\nconst userStore = {\n  findById:       async (id) => db.users.findOne({ id }),\n  findByUsername: async (username) => db.users.findOne({ username }),\n  findByEmail:    async (email) => db.users.findOne({ email }),\n  create:         async (data) => db.users.insert(data),\n  updatePassword: async (id, hash) => db.users.update({ id }, { password: hash }),\n  setEmailVerified: async (id) => db.users.update({ id }, { emailVerified: true }),\n};\n\n// 2. Implement the EmailAdapter for your email provider\nconst emailAdapter = {\n  sendOtp:          async (to, otp) => { console.log(`Sending OTP to ${to}: ${otp}`); return true; },\n  sendVerification: async (to, url) => { console.log(`Sending verification to ${to}: ${url}`); return true; },\n  checkStatus:      async () => true,\n};\n\n// 3. Mount the plugin\nconst app = new Elysia()\n  .use(\n    velvetAuth(userStore, emailAdapter, {\n      jwt: {\n        secret: process.env.JWT_SECRET ?? 'super-secret-jwt-key-that-is-at-least-32-chars-long',\n      },\n      redis: {\n        url: process.env.REDIS_URL ?? 'redis://localhost:6379'\n      },\n      password: {\n        minLength: 8, // Example: enforce min length\n      }\n    }),\n  )\n  .get('/', () => 'Welcome to velvet-auth example!')\n  .listen(3000);\n\nconsole.log(\n  `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`\n);\n","lang":"typescript","description":"This quickstart demonstrates how to integrate `velvet-auth` into an Elysia application, showing the setup of `UserStoreAdapter` and `EmailAdapter` mocks, and mounting the `velvetAuth` plugin with essential JWT and Redis configuration. It also includes a basic Elysia route to confirm server operation."},"warnings":[{"fix":"Ensure that `createAuthGuard` is composed only where the `user` context is explicitly intended to be available. Review any complex app compositions to verify the correct scope of `ctx.user`.","message":"The `createAuthGuard` function in v0.1.9 changed its internal `.derive()` call from `{ as: \"global\" }` to `{ as: \"scoped\" }`. This change prevents the user context from unintentionally leaking into unrelated routes when the guard is composed into a larger Elysia app. While a fix for context isolation, it might affect assumptions in existing applications that relied on the global derivation behavior.","severity":"gotcha","affected_versions":">=0.1.9"},{"fix":"Generate a strong, long (>=32 characters) cryptographic secret for `jwt.secret`. Use environment variables (e.g., `process.env.JWT_SECRET!`) to manage it securely, especially in production environments.","message":"The `jwt.secret` configuration option is critical for security and must be a string with a minimum length of 32 characters. Using a shorter or easily guessable secret makes JWTs vulnerable to brute-force attacks.","severity":"gotcha","affected_versions":">=0.1.0"},{"fix":"Ensure client-side password validation logic aligns with the server-side `config.password` rules to prevent registration failures due to policy violations. Inform users about password requirements during registration.","message":"Earlier versions (before v0.1.8) might have silently ignored password policy rules defined in `config.password` (e.g., `minLength`, `requireUppercase`). Since v0.1.8, these rules are actively enforced during registration and will return a `400` error if violated.","severity":"breaking","affected_versions":">=0.1.8"},{"fix":"Ensure your project is running on Bun version >= 1.0. This package is not compatible with other JavaScript runtimes.","message":"velvet-auth relies heavily on Bun's native features, specifically `Bun.password` for Argon2id hashing and `Bun.RedisClient`. This means it is strictly a Bun-only package and will not work with Node.js or Deno runtimes.","severity":"gotcha","affected_versions":">=0.1.0"}],"env_vars":null,"last_verified":"2026-04-22T00:00:00.000Z","next_check":"2026-07-21T00:00:00.000Z","problems":[{"fix":"Update your `velvetAuth` configuration to provide a `jwt.secret` that is at least 32 characters long. Example: `{ jwt: { secret: process.env.JWT_SECRET || 'your-long-and-secure-secret-key-here' } }`","cause":"The `jwt.secret` configuration option is too short, leading to an insecure JWT setup.","error":"Error: The provided secret must be at least 32 characters long."},{"fix":"Ensure your project is executed using Bun (e.g., `bun run start`) and that your Bun version is >= 1.0.","cause":"The application is likely being run in a Node.js environment instead of Bun, or Bun is an outdated version.","error":"TypeError: Bun.password is not a function"},{"fix":"Verify that your Redis server is running and accessible at the configured `redis.url` (default `redis://localhost:6379`). Check firewall rules and Redis server status. You can configure the URL via `velvetAuth({ redis: { url: 'your_redis_url' } })` or `process.env.REDIS_URL`.","cause":"The application cannot establish a connection to the Redis server, which might be offline, misconfigured, or running on a different port/host.","error":"Error: Redis connection failed: connect ECONNREFUSED 127.0.0.1:6379"},{"fix":"Ensure you are using `createAuthGuard()` on the routes where `ctx.user` is expected. Example: `app.group('/protected', (app) => app.use(createAuthGuard()).get('/', (ctx) => ctx.user))`.","cause":"The `ctx.user` object is not correctly typed or available in the handler, likely because the `createAuthGuard` was not applied, or applied incorrectly to the route.","error":"Property 'user' does not exist on type 'Context'."}],"ecosystem":"npm","meta_description":null}