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:
Parses authentication tokens from HTTP headers or WebSocket messages
Verifies the token signature and expiration
Extracts the user ID from the token payload (subject claim)
Retrieves the full user entity from the database
Returns the authenticated user
Installation
The JWT strategy is included in @feathersjs/authentication:
npm install @feathersjs/authentication --save
Configuration
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 )
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" ]
}
}
}
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:
Option Type Default Description 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 ID Token subject (user ID) jwtidstringUUID v4 Unique 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:
Option Type Default Description headerstring'Authorization'HTTP header to parse schemesstring[]['Bearer', 'JWT']Token scheme prefixes entitystringFrom auth config Entity name servicestringFrom auth config Entity service path entityIdstringFrom auth config Entity 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
Bearer Token
JWT Scheme
Without Scheme
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 } `
}
})
Alternative JWT scheme: curl -H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
https://api.example.com/messages
If no scheme is configured: // config/default.json
{
"authentication" : {
"jwt" : {
"header" : "Authorization" ,
"schemes" : [] // No scheme required
}
}
}
curl -H "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
https://api.example.com/messages
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 ())
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