Skip to content

Repository files navigation

owlauth

owlauth logo


npm package Node.js License

Open-source OWASP-aligned authentication and account-security library for Node.js.

owlauth, published as @restingowlorg/owlauth, gives your Node.js app the core pieces of authentication: credentials login, magic links, password checks, and security-focused audit logging. It works with PostgreSQL and MongoDB and stays out of your framework.

  • Package: @restingowlorg/owlauth
  • Latest stable tag: latest
  • Prerelease tag: next
  • Install: npm install @restingowlorg/owlauth
  • Developer guide: docs/DEVELOPER_GUIDE.md

What You Get

  • Credentials authentication: Sign up users, log them in, and rotate passwords through one library surface.
  • Passwordless magic links: Request, verify, and consume single-use login tokens.
  • PostgreSQL and MongoDB adapters: Use the same auth API across two common persistence stacks.
  • Password hygiene controls: Enforce minimum strength with zxcvbn, reject context-based weak passwords, and check candidate passwords against Have I Been Pwned using the k-anonymity API.
  • Security-focused logging: Audit events are logged with built-in masking for sensitive fields such as passwords, tokens, secrets, cookies, and authorization data.
  • Request tracing support: Pass a correlationId through auth operations to align library logs with your application logs.
  • Pluggable cryptography: Swap the default crypto adapter if your stack requires a different hashing or token strategy.
  • Typed, predictable results: Every auth method returns a consistent AuthResult<T> shape.

Support Matrix

Area Current Support
Runtime Node.js 18+
Language TypeScript, JavaScript
Module output CommonJS
Databases PostgreSQL, MongoDB
Auth flows Credentials, Magic Link

Installation

npm install @restingowlorg/owlauth

Quick Start

import { createAuthManager, PostgresAdapter } from "@restingowlorg/owlauth";

const auth = await createAuthManager({
  adapter: new PostgresAdapter({
    postgresUrl: process.env.POSTGRES_URL!,
    userTableName: "users",
    magicLinkTableName: "magic_links"
  }),
  authTypes: ["credentials", "magicLink"],
  blockedPasswords: ["company-name", "product-name"],
  pwnedPasswordFailClosed: true,
  customMaskingKeys: ["apiKey", "accessToken"]
});

const signup = await auth.credentials.signup(
  "user@example.com",
  "engineer01",
  "CorrectHorseBatteryStaple!2026",
  { correlationId: "req_123" }
);

if (signup.success) {
  console.log(signup.data.user.email);
}

await auth.disconnectDB();

Database Adapters

PostgreSQL

import { createAuthManager, PostgresAdapter } from "@restingowlorg/owlauth";

const auth = await createAuthManager({
  adapter: new PostgresAdapter({
    postgresUrl: process.env.POSTGRES_URL!,
    userTableName: "users",
    userSchema: "public",
    magicLinkTableName: "magic_links",
    magicLinkSchema: "public"
  }),
  authTypes: ["credentials", "magicLink"]
});

MongoDB

import { createAuthManager, MongoAdapter } from "@restingowlorg/owlauth";

const auth = await createAuthManager({
  adapter: new MongoAdapter({
    mongoUri: process.env.MONGO_URI!,
    userCollectionName: "users",
    magicLinkCollectionName: "magic_links"
  }),
  authTypes: ["credentials", "magicLink"]
});

Cryptography

owlauth ships with a default BcryptAdapter (10 rounds). You can customize it or provide your own implementation of ICryptoAdapter.

Customizing Default Crypto

import { createAuthManager, BcryptAdapter } from "@restingowlorg/owlauth";

const auth = await createAuthManager({
  // ... rest of config
  cryptoAdapter: new BcryptAdapter()
});

Implementing Your Own

If you need Argon2, PBKDF2, or a custom token strategy, implement the ICryptoAdapter interface:

import { ICryptoAdapter } from "@restingowlorg/owlauth";

class MyCrypto implements ICryptoAdapter {
  async hashPassword(p: string) {
    /* ... */
  }
  async verifyPassword(p: string, h: string) {
    /* ... */
  }
  generateToken() {
    /* ... */
  }
  async hashToken(t: string) {
    /* ... */
  }
  async verifyToken(t: string, h: string) {
    /* ... */
  }
}

const auth = await createAuthManager({
  // ...
  cryptoAdapter: new MyCrypto()
});

Core Usage

Credentials Flow

import {
  AuthResult,
  SignupResult,
  LoginResult,
  ChangePasswordResult
} from "@restingowlorg/owlauth";

const signup: AuthResult<SignupResult> = await auth.credentials.signup(
  "user@example.com",
  "engineer01",
  "CorrectHorseBatteryStaple!2026"
);

const login: AuthResult<LoginResult> = await auth.credentials.login(
  "user@example.com",
  "CorrectHorseBatteryStaple!2026",
  {
    correlationId: "req_login_001"
  }
);

const passwordChange: AuthResult<ChangePasswordResult> = await auth.credentials.changePassword(
  "user_id123456",
  "current_strong_password",
  "new_strong_password_example",
  {
    correlationId: "req_password_001"
  }
);

Magic Link Flow

import {
  AuthResult,
  RequestMagicLinkResult,
  VerifyMagicLinkResult,
  ConsumeMagicLinkResult
} from "@restingowlorg/owlauth";

const requested: AuthResult<RequestMagicLinkResult> = await auth.magicLink.request(
  "user@example.com",
  { correlationId: "req_magic_001" }
);

if (requested.success) {
  const token = requested.data;
  const verified: AuthResult<VerifyMagicLinkResult> = await auth.magicLink.verify(token);
  const consumed: AuthResult<ConsumeMagicLinkResult> = await auth.magicLink.consume(token);
}

request() returns a composite token string in the format {recordId}.{rawToken}. Both parts are required — verify() and consume() expect the full composite value as-is. Putting it in a URL, sending the email, and handling delivery is your application's job. owlauth does not touch any of that.

Note: The recordId segment is the database record's primary key. It is not sensitive, but treat the full composite token as a secret: it grants one-time login access and must only be transmitted over TLS.

Configuration Options

Shared Options

Option Type Purpose
authTypes ("credentials" | "magicLink")[] Enables one or both supported auth flows. Defaults to credentials only.
blockedPasswords string[] Rejects passwords containing the user's email, username, or any supplied blocked terms.
magicLinkBaseUrl string Base URL for magic link emails. When set, request() returns a ready-to-use URL in the format {baseUrl}?token={token}. When omitted, the raw composite token is returned for manual URL construction.
cryptoAdapter ICryptoAdapter Replaces the default bcrypt-based crypto implementation.
customMaskingKeys string[] Adds case-insensitive keys to the audit logger masking list.
pwnedPasswordFailClosed boolean Rejects signups and password changes when the breached-password API cannot be reached.
usernameValidator (username: string) => boolean Overrides the default username validation rule. Default: 3–20 chars, alphanumeric + underscore only (/^[a-zA-Z0-9_]{3,20}$/). Return true to accept, false to reject.

Note: The built-in PostgresAdapter and MongoAdapter implement findByUsername and enforce username uniqueness at signup. If you supply a custom UserRepository that does not implement findByUsername, duplicate-username detection is skipped silently. Implement the method if your application requires unique usernames.

Method-Level Options

  • signup(): blockedPasswords, pwnedPasswordFailClosed, correlationId
  • login(): correlationId
  • changePassword(): blockedPasswords, pwnedPasswordFailClosed, correlationId
  • magicLink.request(): correlationId
  • magicLink.verify(): correlationId
  • magicLink.consume(): correlationId

Response Model

Every public method returns the same envelope:

type AuthResult<T = unknown> =
  | { success: true; data: T; httpCode: number; message: string }
  | { success: false; data?: undefined; httpCode: number; message: string };

Result payloads are:

  • SignupResult: { user: SafeUser } — where SafeUser = { id, email, username }
  • LoginResult: { user: SafeUser }
  • ChangePasswordResult: { user: SafeUser ; tokensInvalidated: boolean}
  • RequestMagicLinkResult: string — when magicLinkBaseUrl is configured: full URL "{baseUrl}?token={recordId}.{rawToken}" ready to embed in an email. Without magicLinkBaseUrl: raw composite "{recordId}.{rawToken}" for manual URL construction. Pass the token portion directly to verify() and consume().
  • VerifyMagicLinkResult: { isValid: boolean; userId: UserId; lookupKey: string; }
  • ConsumeMagicLinkResult: { userId: UserId }

SafeUser and UserId are exported directly from the package and can be imported for type annotations:

import type { SafeUser, UserId } from "@restingowlorg/owlauth";

OWASP Alignment

Here's exactly what owlauth does, and where each decision comes from. Every control is traced back to the OWASP Authentication Cheat Sheet, ASVS 5.0.0, or OWASP Top 10:2025.

Password Security

Control What the library does OWASP reference
Password strength scoring Candidate passwords are scored with @zxcvbn-ts/core at signup and on every password change. Scores below 3 of 4 are rejected. Auth Cheat Sheet — Implement Proper Password Strength Controls
Breached password check The Have I Been Pwned Pwned Passwords API is queried using the k-anonymity range method. Only the first 5 characters of the SHA-1 hash are transmitted; the raw password never leaves the process. Auth Cheat Sheet — Block previously breached passwords
Context-aware blocking Passwords are rejected if they contain the user's email local part, username, or any caller-supplied blocked terms, preventing context-guessable passwords. NIST SP 800-63B § 5.1.1.2, context-specific word verification
Fail-closed breach check When pwnedPasswordFailClosed: true, a network error on the breach API causes the request to fail rather than silently pass. Defense-in-depth, fail-safe defaults

Credential Storage

Control What the library does OWASP reference
Adaptive password hashing Passwords are hashed with bcrypt at 10 salt rounds via the built-in BcryptAdapter. OWASP Password Storage Cheat Sheet, use bcrypt
Pluggable crypto adapter The ICryptoAdapter interface allows swapping the default bcrypt implementation for any alternative hashing or token strategy without changing the auth API surface. ASVS 5.0, algorithm agility

Authentication Logic

Control What the library does OWASP reference
Generic login error messages Login failure returns "Invalid credentials." regardless of whether the email is unknown or the password is wrong, preventing user enumeration. Magic link request() always returns 200 with a neutral message ("If this email is registered, a magic link has been sent.") regardless of whether the email exists, preventing email enumeration via the passwordless flow. Auth Cheat Sheet, Authentication and Error Messages
Current password re-verification changePassword() requires the caller to supply and verify the current password before a new one is accepted, preventing silent takeover through a hijacked session. Auth Cheat Sheet, Change Password Feature

Passwordless Authentication

Control What the library does OWASP reference
Cryptographically secure token generation Magic link tokens are produced with crypto.randomBytes(32) from Node.js's built-in CSPRNG. OWASP Forgot Password Cheat Sheet, use a cryptographically random value
Token hashing at rest The raw token is never stored. Only the SHA-256 hash of the token is persisted using a timing-safe comparison; the database contains no recoverable plaintext. OWASP Forgot Password Cheat Sheet, hash tokens before storage
Short expiry window Tokens expire 15 minutes after issuance. OWASP Forgot Password Cheat Sheet, use a short token lifetime
Single-use with prior invalidation Requesting a new magic link immediately invalidates all previous active tokens for that user. Consumed tokens cannot be reused. OWASP Forgot Password Cheat Sheet, single-use tokens

Security Logging and Audit Trail

Control What the library does OWASP reference
Security event logging Every auth operation, signup, login, password change, and all magic link steps, emits a structured audit event with event type, email, outcome, and reason. Auth Cheat Sheet, Logging and Monitoring; A09:2025 Security Logging and Alerting Failures
Sensitive field masking The audit logger automatically redacts values at keys matching password, token, secret, authorization, cookie, and apikey. Callers can extend this with customMaskingKeys. ASVS 5.0, do not log sensitive data;OWASP Logging Cheat Sheet
Correlation ID propagation Every auth method accepts an optional correlationId. When supplied, it is included in all log output for that operation, enabling trace correlation with application-level logs. OWASP Logging Cheat Sheet, include trace identifiers

Security Notes

The table above covers what this library actually does. It's not an OWASP certification, and it won't make your app ASVS-compliant on its own. You still need to handle:

  • TLS and secure transport
  • secure email delivery for magic links
  • rate limiting, brute-force protection, and account lockout
  • session management
  • CSRF defenses where relevant
  • account verification and recovery workflows
  • MFA or passkeys if the risk model requires them
  • authorization and role enforcement

Roadmap

owlauth is part of a wider RestingOwl effort focused on building secure-by-default tooling for various technology stacks, not limited to Node.js.

Here's what's coming next:

  • More application stacks: First-party integrations for Express, Fastify, NestJS, Next.js, and serverless Node runtimes
  • More data stores: Additional adapters for MySQL, SQLite, DynamoDB, and other operationally common backends
  • Stronger auth options: WebAuthn, passkeys, TOTP-based MFA, and recovery-oriented flows
  • Operational hardening: Built-in rate limiting hooks, lockout strategies, and safer recovery patterns
  • Broader RestingOwl package family: Adjacent packages for rate limiting, input sanitization, audit logging, secrets management, and CSRF protection

Community

Website LinkedIn GitHub Issues Security Policy Contributing

License

MIT License

About

Open source authentication library

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages