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

@soapjs/soap-auth

v1.0.4

Published

Authentication strategies, sessions, MFA, and token helpers for the SoapJS ecosystem.

Readme

SoapAuth

Authentication strategies, session handling, MFA, and token helpers for the SoapJS ecosystem.

@soapjs/soap-auth provides composable authentication primitives for HTTP and socket applications. It includes JWT, local credentials, basic auth, API key auth, built-in OAuth2 social providers, sessions, roles, rate limiting, account lockout, password helpers, MFA/TOTP, PKCE, and JWKS verification.

The package does not depend on Passport or provider SDKs. Built-in and configurable OAuth2 strategies use platform fetch plus user-provided mapping callbacks, so applications can start quickly and still replace any part of the auth flow with their own implementation.

Installation

npm install @soapjs/soap-auth @soapjs/soap

Requirements

  • Node.js 24.17.0 or newer
  • @soapjs/soap 0.14 or newer

Quick Start

import { createJwtAuthConfig, SoapAuth } from "@soapjs/soap-auth";

const auth = await SoapAuth.create({
  http: {
    jwt: createJwtAuthConfig({
      accessSecret: process.env.JWT_ACCESS_SECRET!,
      refreshSecret: process.env.JWT_REFRESH_SECRET!,
      user: {
        fetchUser: async (payload) => users.findById((payload as any).id),
      },
    }),
  },
});

const result = await auth.getHttpStrategy("jwt").authenticate(context);

Recipes

Recipes are framework-neutral config helpers. They return plain SoapAuth config objects and do not import Express, Passport, provider SDKs, or any other adapter library.

import {
  createApiKeyAuthConfig,
  createExternalIdentityOAuth2ProviderConfig,
  createHybridOAuth2ProviderConfig,
  createJwtAuthConfig,
  createLocalAuthConfig,
  createOAuth2ProviderConfig,
  oauth2ProviderEndpoints,
} from "@soapjs/soap-auth";

Available recipes:

  • createJwtAuthConfig(...)
  • createLocalAuthConfig(...)
  • createBasicAuthConfig(...)
  • createApiKeyAuthConfig(...)
  • createOAuth2ProviderConfig(...)
  • createExternalIdentityOAuth2ProviderConfig(...)
  • createHybridOAuth2ProviderConfig(...)
  • oauth2ProviderEndpoints.auth0(...)
  • oauth2ProviderEndpoints.keycloak(...)
  • oauth2ProviderEndpoints.discord()
  • oauth2ProviderEndpoints.google()
  • oauth2ProviderEndpoints.github()
  • oauth2ProviderEndpoints.facebook()

Recipes are also available from @soapjs/soap-auth/recipes.

Type-only subpath imports are supported for TypeScript projects using classic moduleResolution: node:

import type { StorageContext } from "@soapjs/soap-auth/types";
import type { CookieOptions } from "@soapjs/soap-auth/recipes";

Factory Configuration

SoapAuth.create() registers built-in strategies from config:

  • http.jwt as jwt
  • http.local as local
  • http.basic as basic
  • http.apiKey as api-key
  • http.oauth2.google as google
  • http.oauth2.github as github
  • http.oauth2.facebook as facebook
  • any other http.oauth2.<name> with OAuth2 endpoints as <name>
  • any http.hybridOAuth2.<name> with OAuth2 endpoints as <name>
  • socket.jwt as jwt
  • socket.apiKey as api-key

Custom strategies can be registered through http.custom, socket.custom, or manually with addStrategy(strategy, name, category).

Local Credentials

import { createLocalAuthConfig } from "@soapjs/soap-auth";

const auth = await SoapAuth.create({
  http: {
    local: createLocalAuthConfig({
      credentials: {
        extractCredentials: (ctx: any) => ({
          identifier: ctx.body.email,
          password: ctx.body.password,
        }),
        verifyCredentials: async (identifier, password) =>
          users.verifyPassword(identifier, password),
      },
      user: {
        fetchUser: async (identifier) => users.findByEmail(String(identifier)),
      },
      basePath: "/auth",
    }),
  },
});

API Key

import { createApiKeyAuthConfig } from "@soapjs/soap-auth";

const auth = await SoapAuth.create({
  http: {
    apiKey: createApiKeyAuthConfig({
      keyType: "long-term",
      extractApiKey: (ctx: any) => ctx.headers["x-api-key"] ?? null,
      retrieveUserByApiKey: async (apiKey) => apiKeys.findUser(apiKey),
      isApiKeyExpired: async (apiKey) => apiKeys.isExpired(apiKey),
      trackApiKeyUsage: async (apiKey) => apiKeys.touch(apiKey),
    }),
  },
});

Sessions

import { MemorySessionStore, SoapAuth } from "@soapjs/soap-auth";

const auth = await SoapAuth.create({
  session: {
    secret: process.env.SESSION_SECRET!,
    store: new MemorySessionStore(),
    getSessionId: (ctx: any) =>
      ctx.cookies?.SESSIONID ?? ctx.headers?.["x-session-id"] ?? null,
  },
  http: {
    local: localConfig,
  },
});

MemorySessionStore is useful for tests and local development. Production applications should provide a durable SessionStore backed by a database, Redis, or another shared storage system.

OAuth2 Providers

const auth = await SoapAuth.create({
  http: {
    oauth2: {
      google: {
        clientId: process.env.GOOGLE_CLIENT_ID!,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        redirectUri: "https://example.com/auth/google/callback",
      },
      github: {
        clientId: process.env.GITHUB_CLIENT_ID!,
        clientSecret: process.env.GITHUB_CLIENT_SECRET!,
        redirectUri: "https://example.com/auth/github/callback",
      },
    },
  },
});

Providers with standard OAuth2/OIDC endpoints can use configurable OAuth2. Providers with unusual token exchange, user lookup, or redirect requirements can be implemented as a subclass of OAuth2Strategy or HttpOAuth2Strategy and registered through http.custom.

External Identity OAuth2

Use external identity OAuth2 when Google, GitHub, Auth0, or another provider is only the login method, but your API must still issue its own JWT and enforce its own account provisioning rules.

import {
  createExternalIdentityOAuth2ProviderConfig,
  createJwtAuthConfig,
  SoapAuth,
} from "@soapjs/soap-auth";

const auth = await SoapAuth.create({
  http: {
    jwt: createJwtAuthConfig({
      accessSecret: process.env.JWT_ACCESS_SECRET!,
      refreshSecret: process.env.JWT_REFRESH_SECRET!,
      user: {
        fetchUser: async (payload) => users.findById((payload as any).id),
      },
    }),
    oauth2: {
      google: createExternalIdentityOAuth2ProviderConfig({
        provider: "google",
        clientId: process.env.GOOGLE_CLIENT_ID!,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        redirectUri: "https://example.com/auth/oauth/google/callback",
        externalIdentity: {
          resolveIdentity: async (identity) => {
            if (!identity.email || !identity.emailVerified) return null;
            return users.provisionOrFindByExternalIdentity(identity);
          },
        },
      }),
    },
  },
});

The OAuth provider token is used only to fetch the provider profile. After resolveIdentity returns an application user, soap-auth issues the configured local JWT access and refresh tokens. Feature code should keep depending on JWT/user/roles or policies, not on how the user logged in.

Configurable OAuth2 Providers

Most OAuth2/OIDC providers do not need a custom class. Provide the endpoints and a profile mapper:

import { createOAuth2ProviderConfig } from "@soapjs/soap-auth";

const auth = await SoapAuth.create({
  http: {
    oauth2: {
      auth0: createOAuth2ProviderConfig({
        provider: "auth0",
        clientId: process.env.AUTH0_CLIENT_ID!,
        clientSecret: process.env.AUTH0_CLIENT_SECRET!,
        redirectUri: "https://example.com/auth/auth0/callback",
        presetOptions: { domain: "tenant.auth0.com" },
        user: {
          fetchUser: async () => null,
          validateUser: async (profile: any) => ({
            id: profile.sub,
            email: profile.email,
            username: profile.nickname ?? profile.name,
            picture: profile.picture,
          }),
        },
      }),
    },
  },
});

For providers without a userinfo endpoint, implement user.fetchUser(accessToken) and return your application user directly.

OAuth2 state.persistence and nonce.persistence callbacks receive the current auth context:

state: {
  persistence: {
    store: async (state, context, metadata) => {
      await context.storeInCookie?.(state, { name: metadata?.key ?? "state" });
    },
    read: async (context, key) => context.getFromCookie?.(key ?? "state") ?? null,
    remove: async (context, key) => {
      await context.removeFromCookie?.(key ?? "state");
    },
  },
}

This lets HTTP adapters such as soap-express persist OAuth state and nonce in cookies, sessions, or request-scoped storage without global state.

Configurable Hybrid OAuth2

Hybrid OAuth2 tries existing JWT/session auth first, then falls back to OAuth2. This is useful when browser users log in through OAuth2 but API clients can keep using JWT.

import { createHybridOAuth2ProviderConfig } from "@soapjs/soap-auth";

const auth = await SoapAuth.create({
  session: sessionConfig,
  http: {
    jwt: jwtConfig,
    hybridOAuth2: {
      enterprise: createHybridOAuth2ProviderConfig({
        provider: "enterprise",
        clientId: process.env.IDP_CLIENT_ID!,
        clientSecret: process.env.IDP_CLIENT_SECRET!,
        redirectUri: "https://example.com/auth/enterprise/callback",
        endpoints: {
          authorizationUrl: "https://idp.example.com/authorize",
          tokenUrl: "https://idp.example.com/token",
          userInfoUrl: "https://idp.example.com/userinfo",
        },
        user: {
          fetchUser: async () => null,
          validateUser: async (profile: any) => ({
            id: profile.sub,
            email: profile.email,
          }),
        },
      }),
    },
  },
});

Custom Strategies

When the built-in config is not enough, implement AuthStrategy directly:

const auth = await SoapAuth.create({
  http: {
    custom: {
      internal: {
        async authenticate(ctx: any) {
          const user = await internalAuth.verify(ctx.headers.authorization);
          return user ? { user } : null;
        },
      },
    },
  },
});

No external strategy package is required; custom strategies only need an authenticate(context) method.

MFA, Roles, Rate Limits, and Lockout

Shared controls can be attached to credential strategies:

const localConfig = {
  credentials,
  user,
  routes,
  mfa: {
    isMfaRequired: (user: any) => user.mfaEnabled,
    extractMfaCode: (ctx: any) => ctx.body.mfaCode,
    validateMfaCode: async (user: any, code: string) =>
      mfa.verify(user.id, code),
  },
  role: {
    roles: ["admin"],
    authorizeByRoles: async (user: any, roles: string[]) =>
      roles.includes(user.role),
  },
  rateLimit: {
    checkRateLimit: async (ctx: any) => rateLimiter.isLimited(ctx.ip),
    incrementRequestCount: async (ctx: any) => rateLimiter.increment(ctx.ip),
  },
};

Release Checks

Before publishing:

npm run test:unit
npm run build
npm pack --dry-run
npm audit --omit=dev

The package publishes compiled CommonJS output from build/ and TypeScript declarations.

License

MIT