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 🙏

© 2025 – Pkg Stats / Ryan Hefner

passport-jose

v0.3.2

Published

Passport JWT strategy with EdDSA, ES256 and modern cryptographic algorithm support via JOSE

Downloads

41

Readme

passport-jose

npm version Node.js CI codecov TypeScript Node.js License: MIT

A modern, security-focused Passport strategy for authenticating with JSON Web Tokens using the jose library.

Motivation: This library was created to bring support for modern JWT algorithms like EdDSA (Ed25519/Ed448), ES256, and other contemporary cryptographic standards to Passport JWT middleware. Built from the ground up in TypeScript, it focuses on Bearer token authentication with enhanced security through the modern jose library, designed to secure sessionless API endpoints. While the original passport-jwt remains excellent for traditional use cases, passport-jose leverages cutting-edge algorithms and security features that weren't available in legacy JWT libraries.

Table of Contents

Key Features

  • Enhanced Security: Uses the modern jose library (v6+) with support for modern algorithms like EdDSA
  • Bearer Token Focused: Exclusively supports RFC 6750 Bearer token authentication
  • Full TypeScript: Complete TypeScript implementation with strict typing
  • Modern Standards: Built for ES2023 with latest JavaScript features
  • Native Key Support: Supports CryptoKey, KeyObject, JWK, and Uint8Array formats natively
  • Performance: Optimized JWT verification using native crypto APIs and minimal dependencies
  • Lightweight: Minimal bundle footprint with only essential dependencies (jose + passport-strategy)
  • Multi-tenant Ready: Dynamic key resolution for enterprise applications

Installation

# npm
$ npm install passport-jose

# pnpm
$ pnpm add passport-jose

# yarn
$ yarn add passport-jose

Quick Start

Get up and running with passport-jose in under 2 minutes:

import passport from 'passport';
import { Strategy, ExtractJwt } from 'passport-jose';

// 1. Configure the strategy
const secretKey = new TextEncoder().encode('your-256-bit-secret');

passport.use(new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withSecretOrKey: secretKey,
  algorithms: ['HS256']
}, (payload, done) => {
  // 2. Verify the user
  const user = { id: payload.sub, email: payload.email };
  done(null, user);
}));

// 3. Protect your routes
app.get('/profile',
  passport.authenticate('jwt', { session: false }),
  (req, res) => res.json({ user: req.user })
);

Send requests with the JWT in the Authorization header:

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" http://localhost:3000/profile

Basic Usage

Configure Strategy

The JWT authentication strategy is constructed as follows:

import { Strategy, ExtractJwt, type JoseKey } from 'passport-jose';

new Strategy(options, verify)

options is an object containing configuration for token extraction and verification:

  • withSecretOrKey - JOSE-compatible key for JWT verification (CryptoKey, KeyObject, JWK, or Uint8Array). REQUIRED unless withKeyProvider is provided.
  • withKeyProvider - Dynamic key resolution callback: (request, rawJwtToken, done) => void where done has signature (err: Error | string | null, key?: JoseKey) => void. REQUIRED unless withSecretOrKey is provided.
  • jwtFromRequest - (REQUIRED) Function that extracts the JWT from the request: (req) => string | null
  • issuer - Expected JWT "iss" (Issuer) claim value(s). Makes the claim presence required.
  • audience - Expected JWT "aud" (Audience) claim value(s). Makes the claim presence required.
  • algorithms - List of accepted JWS "alg" values (e.g., ["HS256", "RS256"])
  • subject - Expected JWT "sub" (Subject) claim value. Makes the claim presence required.
  • maxTokenAge - Maximum time elapsed from JWT "iat" claim (e.g., "1h", 3600)
  • clockTolerance - Clock skew tolerance for time-based claims (e.g., "30s", 30)
  • typ - Expected JWT "typ" (Type) header parameter value
  • passReqToCallback - If true, request is passed to verify callback as first argument

verify is a function with the parameters verify(jwt_payload, done) or verify(req, jwt_payload, done) if passReqToCallback: true

Example Configuration

import passport from 'passport';
import { Strategy, ExtractJwt, type JoseKey } from 'passport-jose';

// Using a symmetric key (Uint8Array)
const secretKey: JoseKey = new TextEncoder().encode('your-256-bit-secret');

const options = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withSecretOrKey: secretKey,
  algorithms: ['HS256'],
  issuer: 'accounts.examplesoft.com',
  audience: 'yoursite.net'
};

passport.use(new Strategy(options, (payload, done) => {
  // payload is typed as jose.JWTPayload
  // Note: User.findOne is pseudo-code - replace with your user lookup logic
  User.findOne({ id: payload.sub }, (err, user) => {
    if (err) return done(err, false);
    if (user) return done(null, user);
    return done(null, false);
  });
}));

Using Different Key Formats

// JWK format
const jwkKey: JoseKey = {
  kty: 'RSA',
  use: 'sig',
  n: '...',
  e: 'AQAB'
};

// Web Crypto API key
const cryptoKey: JoseKey = await crypto.subtle.importKey(/* ... */);

// Node.js KeyObject
const keyObject: JoseKey = crypto.createSecretKey(Buffer.from('secret'));

JWT Extraction Methods

The JWT must be extracted from the request using a user-supplied extractor function passed as the jwtFromRequest parameter.

Included Extractors

  • ExtractJwt.fromHeader(header_name) - Extracts JWT from the specified HTTP header
  • ExtractJwt.fromBodyField(field_name) - Extracts JWT from the request body field
  • ExtractJwt.fromAuthHeaderAsBearerToken() - Extracts JWT from Authorization header with Bearer scheme (recommended)
  • ExtractJwt.fromExtractors([extractors]) - Tries multiple extractors in sequence

Custom Extractor Example

const cookieExtractor = (req: any): string | null => {
  let token = null;
  if (req && req.cookies) {
    token = req.cookies['jwt'];
  }
  return token;
};

const options = {
  jwtFromRequest: cookieExtractor,
  withSecretOrKey: secretKey
};

Authenticate Requests

Use passport.authenticate() specifying 'jwt' as the strategy:

app.post('/profile',
  passport.authenticate('jwt', { session: false }),
  (req, res) => {
    res.send(req.user.profile);
  }
);

Include JWT in Requests

When using ExtractJwt.fromAuthHeaderAsBearerToken(), include the JWT in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Advanced Topics

Remote JWKS Integration

import { Strategy, ExtractJwt, fromRemoteJwks } from 'passport-jose';

// Auth0 JWKS integration
const auth0Strategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withKeyProvider: fromRemoteJwks('https://dev-example.auth0.com/.well-known/jwks.json', {
    cacheMaxAge: 600000, // 10 minutes
    cooldownDuration: 30000, // 30 seconds
  }),
  algorithms: ['RS256'],
  issuer: 'https://dev-example.auth0.com/',
  audience: 'https://api.myapp.com'
}, (payload, done) => {
  User.findById(payload.sub, done);
});

// Azure AD JWKS integration
const azureStrategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withKeyProvider: fromRemoteJwks('https://login.microsoftonline.com/tenant/discovery/v2.0/keys', {
    cacheMaxAge: 300000, // 5 minutes
    timeoutDuration: 10000 // 10 seconds
  }),
  algorithms: ['RS256'],
  issuer: 'https://login.microsoftonline.com/tenant/v2.0'
}, (payload, done) => {
  User.findById(payload.sub || payload.oid, done);
});

Basic JWKS Usage

import { Strategy, ExtractJwt, fromRemoteJwks } from 'passport-jose';

// Auth0 integration
const strategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withKeyProvider: fromRemoteJwks('https://your-domain.auth0.com/.well-known/jwks.json', {
    cacheMaxAge: 600000, // 10 minutes
    cooldownDuration: 30000, // 30 seconds between requests
    timeoutDuration: 5000, // 5 seconds request timeout
  }),
  algorithms: ['RS256'],
  issuer: 'https://your-domain.auth0.com/',
  audience: 'your-api-identifier'
}, (payload, done) => {
  User.findById(payload.sub, done);
});

Popular Identity Provider Configurations

// Example verify function (implement according to your user model)
const verifyUser = (payload, done) => {
  User.findById(payload.sub, (err, user) => {
    if (err) return done(err, false);
    if (user) return done(null, user);
    return done(null, false);
  });
};

// Auth0
const auth0Strategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withKeyProvider: fromRemoteJwks('https://dev-example.auth0.com/.well-known/jwks.json', {
    cacheMaxAge: 600000,
    cooldownDuration: 30000,
  }),
  algorithms: ['RS256'],
  issuer: 'https://dev-example.auth0.com/',
  audience: 'https://api.myapp.com'
}, verifyUser);

// Azure Active Directory
const azureStrategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withKeyProvider: fromRemoteJwks('https://login.microsoftonline.com/common/discovery/v2.0/keys', {
    cacheMaxAge: 300000, // 5 minutes (Azure keys rotate frequently)
    timeoutDuration: 10000,
  }),
  algorithms: ['RS256'],
  issuer: 'https://login.microsoftonline.com/{tenant}/v2.0'
}, verifyUser);

// AWS Cognito
const cognitoStrategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withKeyProvider: fromRemoteJwks('https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json', {
    cacheMaxAge: 3600000, // 1 hour
    cooldownDuration: 60000,
  }),
  algorithms: ['RS256'],
  issuer: 'https://cognito-idp.{region}.amazonaws.com/{userPoolId}'
}, verifyUser);

// Google Identity Platform
const googleStrategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withKeyProvider: fromRemoteJwks('https://www.googleapis.com/oauth2/v3/certs', {
    cacheMaxAge: 86400000, // 24 hours
    cooldownDuration: 30000,
  }),
  algorithms: ['RS256'],
  issuer: 'https://accounts.google.com'
}, verifyUser);

JWKS Configuration Options

interface RemoteJWKSetOptions {
  /**
   * Duration for which the JWKS is cached (default: 600000ms / 10 minutes)
   */
  cacheMaxAge?: number;

  /**
   * Cooldown period between JWKS requests (default: 30000ms / 30 seconds)
   */
  cooldownDuration?: number;

  /**
   * HTTP request timeout for JWKS fetching (default: 5000ms / 5 seconds)
   */
  timeoutDuration?: number;

  /**
   * HTTP agent for custom connection handling
   */
  agent?: any;

  /**
   * Additional headers to include in JWKS requests
   */
  headers?: Record<string, string>;
}

Multi-tenant Support

import { Strategy, ExtractJwt, type SecretOrKeyProvider } from 'passport-jose';
import { decodeProtectedHeader } from 'jose';

const keyProvider: SecretOrKeyProvider = (request, rawJwtToken, done) => {
  // Extract tenant from JWT header or request
  const decoded = decodeProtectedHeader(rawJwtToken);
  const tenantId = decoded.kid || request.headers['x-tenant-id'];

  // Fetch tenant-specific key
  getTenantKey(tenantId)
    .then(key => done(null, key))
    .catch(err => done(err));
};

const strategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withKeyProvider: keyProvider,
  algorithms: ['RS256', 'ES256'],
  issuer: 'https://auth.myapp.com'
}, (payload, done) => {
  // Verify user with tenant context
  User.findInTenant(payload.tenant, payload.sub, done);
});

EdDSA Keys

import { generateKeyPair } from 'crypto';
import { Strategy, ExtractJwt } from 'passport-jose';

// Generate EdDSA key pair
const { publicKey, privateKey } = generateKeyPair('ed25519', {
  publicKeyEncoding: { type: 'spki', format: 'pem' },
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

const strategy = new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withSecretOrKey: publicKey,
  algorithms: ['EdDSA']
}, (payload, done) => {
  User.findById(payload.sub, done);
});

Custom JWT Extractors

const cookieExtractor = (req: any): string | null => {
  let token = null;
  if (req && req.cookies) {
    token = req.cookies['jwt'];
  }
  return token;
};

const options = {
  jwtFromRequest: cookieExtractor,
  withSecretOrKey: secretKey
};

Framework Integration

NestJS Integration

Here's how to integrate passport-jose with NestJS:

// auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ConfigService } from '@nestjs/config';
import { Strategy, ExtractJwt, fromRemoteJwks } from 'passport-jose';

const CACHE_MAX_AGE = 600_000; // 10 minutes

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(configService: ConfigService) {
    const jwksUrl = configService.get<string>('IDENTITY_PROVIDER_URL') + '/.well-known/jwks.json';

    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      withKeyProvider: fromRemoteJwks(jwksUrl, { cacheMaxAge: CACHE_MAX_AGE }),
      algorithms: ['RS256'],
      issuer: configService.get<string>('JWT_ISSUER'),
      audience: configService.get<string>('JWT_AUDIENCE'),
    });
  }

  validate({ sub, email }: { sub: string; email: string }) {
    return { userId: sub, email };
  }
}

// auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtStrategy } from './jwt.strategy';

@Module({
  providers: [JwtStrategy],
})
export class AuthModule {}

// types/express.d.ts - Type augmentation for Express
declare namespace Express {
  namespace Request {
    interface User {
      userId: string;
      email: string;
    }
  }

  interface Request {
    user?: Request.User;
  }
}

type ContextUser = Express.Request.User;

// middleware/auth/auth.guard.ts - HTTP Authentication Guards
import { Injectable, ExecutionContext, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class HttpAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext) {
    return context.switchToHttp().getRequest();
  }
}

@Injectable()
export class HttpOptionalAuthGuard extends HttpAuthGuard {
  handleRequest<TUser>(err: any, user: TUser): TUser | null {
    if (err) {
      throw err;
    }
    return user;
  }
}

// Guard Decorators
export const WithAuth = UseGuards(HttpAuthGuard);
export const WithOptionalAuth = UseGuards(HttpOptionalAuthGuard);

// middleware/auth/current-user.decorator.ts - Current user decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (_: unknown, context: ExecutionContext): ContextUser => {
    return context.switchToHttp().getRequest().user;
  },
);

// app.controller.ts - Example HTTP Controller Usage
import { Controller, Get } from '@nestjs/common';
import { WithAuth, WithOptionalAuth } from './middleware/auth/auth.guard';
import { CurrentUser } from './middleware/auth/current-user.decorator';

@Controller('api')
export class AppController {
  @Get('profile')
  @WithAuth
  getProfile(@CurrentUser() user: ContextUser) {
    return {
      message: 'Protected route',
      user,
    };
  }

  @Get('dashboard')
  @WithOptionalAuth
  getDashboard(@CurrentUser() user: ContextUser | null) {
    return {
      message: user ? 'Authenticated dashboard' : 'Public dashboard',
      user,
    };
  }

  @Get('public')
  getPublic() {
    return { message: 'Public route' };
  }
}

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { AppController } from './app.controller';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    AuthModule,
  ],
  controllers: [AppController],
})
export class AppModule {}

Environment variables (.env):

IDENTITY_PROVIDER_URL=https://auth.example.com
JWT_ISSUER=https://auth.example.com
JWT_AUDIENCE=my-api

Migration from @nestjs/passport + passport-jwt:

  1. Replace passport-jwt with passport-jose
  2. Update strategy options: secretOrKeywithSecretOrKey
  3. Consider using fromRemoteJwks() for better key management
  4. Add required algorithms array
  5. Replace ignoreExpiration with maxTokenAge if needed

Express.js Integration

import express from 'express';
import passport from 'passport';
import { Strategy, ExtractJwt } from 'passport-jose';

const app = express();

passport.use('jwt', new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withSecretOrKey: new TextEncoder().encode(process.env.JWT_SECRET),
  algorithms: ['HS256'],
  issuer: process.env.JWT_ISSUER,
  maxTokenAge: '1h'
}, async (payload, done) => {
  try {
    const user = await User.findById(payload.sub);
    if (!user) return done(null, false, { message: 'User not found' });
    return done(null, user);
  } catch (error) {
    return done(error);
  }
}));

// Protected route with custom error handling
app.get('/api/profile',
  passport.authenticate('jwt', { session: false }),
  (req, res) => {
    res.json({ user: req.user });
  }
);

// Custom error handler for JWT failures
app.use((err, req, res, next) => {
  if (err.name === 'JWTInvalid' || err.name === 'JWTExpired') {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
  next(err);
});

GraphQL Integration

import { ApolloServer } from 'apollo-server-express';
import passport from 'passport';
import { Strategy, ExtractJwt } from 'passport-jose';

passport.use(new Strategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  withSecretOrKey: new TextEncoder().encode(process.env.JWT_SECRET),
  algorithms: ['HS256']
}, (payload, done) => {
  User.findById(payload.sub, done);
}));

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    return new Promise((resolve, reject) => {
      passport.authenticate('jwt', { session: false }, (err, user) => {
        if (err) return reject(err);
        resolve({ user, req });
      })(req);
    });
  }
});

Migration from passport-jwt

This library is not a drop-in replacement for passport-jwt. Key differences:

What's Changed

  • Property Names: secretOrKeywithSecretOrKey, secretOrKeyProviderwithKeyProvider
  • Library: Uses jose library instead of jsonwebtoken
  • Authentication: Only supports Bearer token authentication
  • Expiration: Removed ignoreExpiration option (use maxTokenAge instead)
  • Headers: Removed fromAuthHeaderWithScheme (use Bearer tokens only)
  • Keys: Different key format support (jose native types)
  • JWKS: Added remote JWKS support with fromRemoteJwks()

Migration Steps

  1. Update imports: passport-jwtpassport-jose
  2. Update property names:
    • secretOrKeywithSecretOrKey
    • secretOrKeyProviderwithKeyProvider
  3. Convert keys to jose-compatible formats (CryptoKey, KeyObject, JWK, Uint8Array)
  4. Replace options:
    • ignoreExpirationmaxTokenAge
    • Add required algorithms array
  5. Use Bearer extraction: ExtractJwt.fromAuthHeaderAsBearerToken()
  6. Add JWKS support: Consider using fromRemoteJwks() for production
  7. Update TypeScript types: Import types from passport-jose

Troubleshooting

Common Issues

"Invalid JWT" errors

  • Ensure your JWT token is properly formatted and signed with the correct key
  • Verify that the algorithm specified in algorithms array matches the JWT's signing algorithm
  • Check that the JWT hasn't expired (use maxTokenAge to control this)

"No auth token" errors

  • Verify that your jwtFromRequest extractor is correctly configured
  • For Bearer tokens, ensure the Authorization header format is: Authorization: Bearer <token>
  • Check that the token is being included in the request

Type errors with keys

// ❌ Wrong - raw string
withSecretOrKey: 'my-secret'

// ✅ Correct - Uint8Array for symmetric keys
withSecretOrKey: new TextEncoder().encode('my-secret')

// ✅ Correct - for asymmetric keys
withSecretOrKey: fs.readFileSync('public-key.pem')

// ✅ Best - JWKS for production
withKeyProvider: fromRemoteJwks('https://auth.example.com/.well-known/jwks.json', {
  cacheMaxAge: 600000
})

Claims validation failures

  • When using issuer, audience, or subject options, ensure your JWTs include these claims
  • Use clockTolerance option if you're experiencing time-related validation issues

Debugging Tips

  1. Enable debug logging: Set DEBUG=passport-jose:* environment variable
  2. Test JWT tokens: Use jwt.io to decode and verify your tokens
  3. Check extractor: Test your jwtFromRequest function independently:
    const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req);
    console.log('Extracted token:', token);
  4. Validate keys: Ensure your signing and verification keys match:
    // For symmetric keys, both should be identical
    const signingKey = new TextEncoder().encode('secret');
    const verificationKey = new TextEncoder().encode('secret');
    
    // For asymmetric keys, public key verifies what private key signed
  5. Check claims: Log the payload in your verify callback:
    passport.use(new Strategy(options, (payload, done) => {
      console.log('JWT Payload:', payload);
      // ... rest of verification
    }));

Environment-Specific Issues

Development vs Production

  • Development: Consider using longer maxTokenAge for easier testing
  • Production: Use shorter expiration times and implement refresh token patterns

CORS and Preflight Requests

  • Ensure your CORS configuration allows Authorization headers
  • Preflight OPTIONS requests don't include Authorization headers

Load Balancers and Proxies

  • Verify that Authorization headers are forwarded correctly
  • Some load balancers may strip or modify headers

API Reference

Strategy Options

interface StrategyOptions {
  jwtFromRequest: JwtFromRequestFunction;
  withSecretOrKey?: JoseKey;
  withKeyProvider?: SecretOrKeyProvider;
  issuer?: string | string[];
  audience?: string | string[];
  algorithms?: string[];
  subject?: string;
  maxTokenAge?: string | number;
  clockTolerance?: string | number;
  typ?: string;
  passReqToCallback?: boolean;
}

Type Definitions

import type { CryptoKey, KeyObject, JWK, JWTVerifyGetKey } from 'jose';

// Re-exported `jose` types
type JoseKey = CryptoKey | KeyObject | JWK | Uint8Array;

interface JwtFromRequestFunction<T = any> {
  (req: T): string | null;
}

interface SecretOrKeyProvider<T = any> {
  (request: T, rawJwtToken: string, done: (err: Error | string | null, secretOrKey?: JoseKey | JWTVerifyGetKey) => void): void;
}

// JWKS Provider function
function fromRemoteJwks(
  jwksUri: string,
  options: RemoteJWKSetOptions
): SecretOrKeyProvider;

Development

Build and Test

# Install dependencies
yarn install

# Build TypeScript
yarn build

# Run tests
yarn test

# Run tests with coverage
yarn test:coverage

Requirements

  • Node.js 20+ (minimum required version)
  • TypeScript 5.0+ for development

Performance Characteristics

  • JWT Verification: ~10-50μs per token (varies by algorithm and key type)
  • JWKS Caching: Configurable cache reduces remote key fetches
  • Memory Usage: Minimal overhead with efficient key caching
  • Bundle Size: ~150KB total (including dependencies)

Compatibility

  • Node.js: 20.0.0+
  • TypeScript: 5.0+
  • Passport: 0.4.0+
  • JOSE Library: 6.0.0+

Security Considerations

Production Best Practices

  • HTTPS Only: Always use HTTPS in production to prevent token interception
  • Strong Secrets: Use cryptographically strong, randomly generated secrets (minimum 256 bits for HS256)
  • Key Rotation: Implement regular key rotation strategies, especially for symmetric keys
  • Token Expiration: Set appropriate token expiration times with maxTokenAge (recommend 15-60 minutes for access tokens)
  • Algorithm Whitelist: Always specify the algorithms array to prevent algorithm confusion attacks
  • Claim Validation: Validate all JWT claims that are relevant to your application security model

Key Management

// ✅ Good: Use strong, randomly generated secrets
const secret = crypto.randomBytes(32); // 256 bits

// ✅ Better: Use asymmetric keys for distributed systems
const { publicKey, privateKey } = generateKeyPair('ed25519');

// ✅ Best: Use JWKS for production environments
withKeyProvider: fromRemoteJwks('https://auth.example.com/.well-known/jwks.json')

Common Security Vulnerabilities

  • Algorithm Confusion: Prevented by specifying algorithms array
  • Key Confusion: Use different keys for different purposes
  • Timing Attacks: The jose library provides constant-time comparisons
  • Token Replay: Consider implementing nonce/jti claims for critical operations

Monitoring and Logging

  • Log authentication failures for security monitoring
  • Monitor for unusual token usage patterns
  • Set up alerts for JWKS endpoint failures
  • Track token expiration and refresh patterns

License

The MIT License

Copyright (c) 2025 asyne

Credits

This project is inspired by and builds upon the excellent work of Mike Nicholson and the original passport-jwt library. We're grateful for the foundation provided by the original passport-jwt project.