Skip to main content
The JWT (JSON Web Token) strategy authenticates requests using signed JWT access tokens. It’s the primary method for authenticating API requests and WebSocket connections after initial login.

How JWT Strategy Works

The JWT strategy:
  1. Parses authentication tokens from HTTP headers or WebSocket messages
  2. Verifies the token signature and expiration
  3. Extracts the user ID from the token payload (subject claim)
  4. Retrieves the full user entity from the database
  5. Returns the authenticated user

Installation

The JWT strategy is included in @feathersjs/authentication:
npm install @feathersjs/authentication --save

Configuration

1

Register JWT Strategy

Register the JWT strategy with your authentication service:
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'

const authentication = new AuthenticationService(app)
authentication.register('jwt', new JWTStrategy())

app.use('/authentication', authentication)
2

Configure JWT Options

Set JWT options in your application configuration:
// config/default.json
{
  "authentication": {
    "secret": "your-secret-key",
    "entity": "user",
    "service": "users",
    "authStrategies": ["jwt", "local"],
    "jwtOptions": {
      "header": { "typ": "access" },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "jwt": {
      "header": "Authorization",
      "schemes": ["Bearer", "JWT"]
    }
  }
}
3

Protect Services

Use the authenticate hook to require JWT authentication:
import { authenticate } from '@feathersjs/authentication'

app.service('users').hooks({
  before: {
    all: [authenticate('jwt')]
  }
})

Configuration Options

JWT Options (jwtOptions)

These options control JWT token creation:
OptionTypeDefaultDescription
headerobject{ typ: 'access' }JWT header claims
audiencestring'https://yourdomain.com'Token audience (who can use it)
issuerstring'feathers'Token issuer (who created it)
algorithmstring'HS256'Signing algorithm
expiresInstring|number'1d'Token expiration time
subjectstringEntity IDToken subject (user ID)
jwtidstringUUID v4Unique token identifier
Expiration formats: '1d', '2h', '30m', '60s', or seconds as number Algorithms: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512

JWT Strategy Options (jwt)

These options control JWT token parsing from HTTP requests:
OptionTypeDefaultDescription
headerstring'Authorization'HTTP header to parse
schemesstring[]['Bearer', 'JWT']Token scheme prefixes
entitystringFrom auth configEntity name
servicestringFrom auth configEntity service path
entityIdstringFrom auth configEntity ID property
JWT strategy options must be configured in the jwt section, not as top-level strategy options. Setting invalid options will throw an error.
// strategy.ts:80-89
// Allowed keys: entity, entityId, service, header, schemes
// All other options should be in jwtOptions

Token Creation

Tokens are created by the authentication service after successful login:
// Login with credentials
const result = await app.service('authentication').create({
  strategy: 'local',
  email: '[email protected]',
  password: 'password123'
})

console.log(result)
// {
//   accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
//   authentication: {
//     strategy: 'local',
//     payload: {
//       sub: '123',           // User ID
//       iat: 1234567890,      // Issued at
//       exp: 1234654290,      // Expires at
//       aud: 'https://yourdomain.com',
//       iss: 'feathers',
//       jti: 'uuid-v4-string'
//     }
//   },
//   user: { id: '123', email: '[email protected]' }
// }

Manual Token Creation

Create tokens programmatically:
const authService = app.service('authentication')

// Create token for a user
const accessToken = await authService.createAccessToken(
  { userId: '123' },  // Payload
  { expiresIn: '7d' } // Options override
)

Token Verification

Tokens are automatically verified by the JWT strategy:
// jwt.ts:134-163
// 1. Extract token from request
// 2. Verify signature and expiration
// 3. Extract user ID from payload
// 4. Fetch user from database
// 5. Return authentication result

async authenticate(authentication, params) {
  const { accessToken } = authentication
  
  if (!accessToken) {
    throw new NotAuthenticated('No access token')
  }
  
  // Verify token signature and expiration
  const payload = await this.authentication.verifyAccessToken(
    accessToken, 
    params.jwt
  )
  
  // Get user ID from token subject
  const entityId = await this.getEntityId(result, params)
  const user = await this.getEntity(entityId, params)
  
  return {
    accessToken,
    authentication: { strategy: 'jwt', accessToken, payload },
    user
  }
}

Manual Token Verification

try {
  const payload = await app.service('authentication')
    .verifyAccessToken(token)
  
  console.log('Valid token for user:', payload.sub)
} catch (error) {
  console.error('Invalid token:', error.message)
}

HTTP Authentication

The most common format:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  https://api.example.com/messages
JavaScript:
fetch('https://api.example.com/messages', {
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
})

WebSocket Authentication

Authenticate WebSocket connections:
import io from 'socket.io-client'

// Connect with authentication
const socket = io('https://api.example.com', {
  auth: {
    strategy: 'jwt',
    accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
  }
})

// Or authenticate after connection
socket.emit('create', 'authentication', {
  strategy: 'jwt',
  accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
})

Connection Management

The JWT strategy automatically manages WebSocket connections:
// jwt.ts:32-78
// On login:
// 1. Calculate time until token expires
// 2. Set timer to disconnect connection
// 3. Store authentication info on connection

// On logout or disconnect:
// 1. Clear authentication info
// 2. Cancel expiration timer
// 3. Remove user reference
Connections are automatically closed when tokens expire, forcing clients to re-authenticate with a fresh token.

Token Refresh

Implement token refresh by re-authenticating with an existing token:
// Client-side token refresh
async function refreshToken() {
  const currentToken = localStorage.getItem('accessToken')
  
  try {
    const result = await app.service('authentication').create({
      strategy: 'jwt',
      accessToken: currentToken
    })
    
    // Store new token
    localStorage.setItem('accessToken', result.accessToken)
    return result.accessToken
  } catch (error) {
    // Token invalid or expired, redirect to login
    window.location.href = '/login'
  }
}

// Refresh before token expires
setInterval(refreshToken, 23 * 60 * 60 * 1000) // 23 hours for 1d tokens

Custom Entity Queries

Customize the query used to fetch the user entity:
import { JWTStrategy } from '@feathersjs/authentication'

class CustomJWTStrategy extends JWTStrategy {
  async getEntityQuery(params) {
    return {
      $select: ['id', 'email', 'roles', 'permissions'],
      isActive: true
    }
  }
}

authentication.register('jwt', new CustomJWTStrategy())

Custom Entity ID Extraction

Extract user ID from custom token claims:
class CustomJWTStrategy extends JWTStrategy {
  async getEntityId(authResult, params) {
    // Default uses token 'sub' claim
    // Override to use custom claim
    return authResult.authentication.payload.userId ||
           authResult.authentication.payload.sub
  }
}

Security Best Practices

Critical Security Considerations

Secret Management

// ❌ Never hardcode secrets
const config = {
  authentication: {
    secret: 'my-secret-key'  // DON'T DO THIS
  }
}

// ✅ Use environment variables
const config = {
  authentication: {
    secret: process.env.AUTH_SECRET
  }
}

Token Expiration

// Balance security and user experience
const jwtOptions = {
  expiresIn: '1d',    // Web apps: short-lived
  // expiresIn: '30d' // Mobile apps: longer-lived with refresh
}

Algorithm Selection

// Symmetric algorithms (HS256) - single secret
jwtOptions: {
  algorithm: 'HS256',
  secret: 'shared-secret'
}

// Asymmetric algorithms (RS256) - public/private key pair
jwtOptions: {
  algorithm: 'RS256',
  secret: privateKey,
  // Clients verify with publicKey
}

Token Validation

// Always validate audience and issuer
jwtOptions: {
  audience: 'https://yourdomain.com',
  issuer: 'feathers'
}

// On verification:
verifyOptions: {
  audience: 'https://yourdomain.com',
  issuer: 'feathers',
  algorithms: ['HS256']  // Prevent algorithm confusion attacks
}

HTTPS Only

Always use HTTPS in production to prevent token interception.Consider setting secure cookie flags and HTTP Strict Transport Security (HSTS) headers.

Troubleshooting

Token Verification Fails

// Error: Invalid token signature
// Cause: Secret mismatch or token tampered
// Solution: Verify secret is consistent across servers

// Error: Token expired
// Cause: Token past expiration time
// Solution: Implement token refresh or re-authenticate

// Error: Invalid algorithm
// Cause: Token signed with different algorithm
// Solution: Verify algorithm configuration matches

User Not Found

// Error: Could not get entity
// Cause: User ID in token doesn't exist in database
// Solution: User may have been deleted, invalidate token

class CustomJWTStrategy extends JWTStrategy {
  async getEntity(id, params) {
    try {
      return await super.getEntity(id, params)
    } catch (error) {
      throw new NotAuthenticated('User no longer exists')
    }
  }
}

Configuration Errors

// Error: Invalid JwtStrategy option 'authentication.jwt.expiresIn'
// Cause: JWT option in wrong configuration section
// Solution: Move to jwtOptions

// ❌ Wrong
{
  "authentication": {
    "jwt": {
      "expiresIn": "1d"  // Wrong location
    }
  }
}

// ✅ Correct
{
  "authentication": {
    "jwtOptions": {
      "expiresIn": "1d"  // Correct location
    }
  }
}

Next Steps

Local Strategy

Add username/password authentication

OAuth Strategy

Enable social login with OAuth

Build docs developers (and LLMs) love