The @feathersjs/authentication-local package provides local username and password authentication using bcrypt for password hashing.
Installation
npm install @feathersjs/authentication-local
LocalStrategy
Authentication strategy for username/password authentication with bcrypt password hashing.
Configuration
Configure the local strategy in your authentication settings:
interface LocalStrategyConfiguration {
usernameField: string
passwordField: string
hashSize?: number
service?: string
entity?: string
entityId?: string
errorMessage?: string
entityPasswordField?: string
entityUsernameField?: string
}
The field name for the username in the authentication request (e.g., ‘email’, ‘username’)
The field name for the password in the authentication request
The bcrypt hash size (cost factor). Higher values are more secure but slower
The path of the entity service (inherited from main auth config)
The name of the entity (inherited from main auth config)
The entity id field (inherited from main auth config)
errorMessage
string
default:"Invalid login"
The error message to show when authentication fails
The field name in the entity for the password (defaults to passwordField). Supports dot notation
The field name in the entity for the username (defaults to usernameField). Supports dot notation
Example:
// config/default.json
{
"authentication": {
"secret": "your-secret-key",
"authStrategies": ["jwt", "local"],
"service": "users",
"entity": "user",
"local": {
"usernameField": "email",
"passwordField": "password",
"hashSize": 10
}
}
}
Setup
Register the local strategy with your authentication service:
import { AuthenticationService } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
const authService = new AuthenticationService(app)
authService.register('local', new LocalStrategy())
Methods
authenticate
Authenticate a user with username and password.
strategy.authenticate(
data: AuthenticationRequest,
params: Params
): Promise<AuthenticationResult>
data
AuthenticationRequest
required
The authentication request containing username and password
Authentication metadata with strategy name
The authenticated user entity
Example:
const result = await app.service('authentication').create({
strategy: 'local',
email: '[email protected]',
password: 'secret123'
})
console.log(result.user) // User object
console.log(result.accessToken) // JWT token
hashPassword
Hash a plain text password using bcrypt.
strategy.hashPassword(
password: string,
params: Params
): Promise<string>
The plain text password to hash
Example:
const localStrategy = authService.getStrategy('local')
const hashedPassword = await localStrategy.hashPassword('secret123', {})
comparePassword
Compare a plain text password with a hashed password.
strategy.comparePassword(
entity: any,
password: string
): Promise<any>
The entity containing the hashed password
The plain text password to compare
findEntity
Find an entity by username.
strategy.findEntity(
username: string,
params: Params
): Promise<any>
The username to search for
getEntityQuery
Build the query for finding an entity. Override this to customize the query.
strategy.getEntityQuery(
query: Query,
params: Params
): Promise<Query>
Example:
class CustomLocalStrategy extends LocalStrategy {
async getEntityQuery(query, params) {
return {
...query,
$limit: 1,
isActive: true // Only find active users
}
}
}
Hooks
hashPassword (deprecated)
This hook is deprecated. Use the passwordHash resolver instead for better type safety and integration with Feathers schemas.
Hash a password field before saving to the database.
import { hooks } from '@feathersjs/authentication-local'
hooks.hashPassword(field: string, options?: HashPasswordOptions)
Configuration optionsinterface HashPasswordOptions {
authentication?: string
strategy?: string
}
Example:
import { hooks } from '@feathersjs/authentication-local'
app.service('users').hooks({
before: {
create: [hooks.hashPassword('password')],
update: [hooks.hashPassword('password')],
patch: [hooks.hashPassword('password')]
}
})
protect (deprecated)
This hook is deprecated. Use Feathers schema dispatch resolvers for safe data representations.
Remove fields from the response to prevent exposing sensitive data.
import { hooks } from '@feathersjs/authentication-local'
hooks.protect(...fields: string[])
Field names to remove from the response
Example:
import { hooks } from '@feathersjs/authentication-local'
app.service('users').hooks({
after: {
all: [hooks.protect('password')]
}
})
passwordHash Resolver
The recommended way to hash passwords using Feathers schemas.
import { passwordHash } from '@feathersjs/authentication-local'
passwordHash(options: { service?: string; strategy: string })
The authentication service name (defaults to ‘authentication’)
Example:
import { resolve } from '@feathersjs/schema'
import { passwordHash } from '@feathersjs/authentication-local'
// User data schema
export const userDataResolver = resolve({
properties: {
password: async (value, user, context) => {
// Hash the password if it's being set
return passwordHash({ strategy: 'local' })(value, user, context)
}
}
})
// Apply to service
app.service('users').hooks({
before: {
create: [resolveData(userDataResolver)],
update: [resolveData(userDataResolver)],
patch: [resolveData(userDataResolver)]
}
})
Protecting Password Fields
Use Feathers schema dispatch resolvers to exclude password fields:
import { resolve } from '@feathersjs/schema'
export const userDispatchResolver = resolve({
properties: {
password: async () => undefined // Remove password from responses
}
})
app.service('users').hooks({
after: {
all: [resolveDispatch(userDispatchResolver)]
}
})
Complete Example
Server Setup
import { feathers } from '@feathersjs/feathers'
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
import { resolve } from '@feathersjs/schema'
import { passwordHash } from '@feathersjs/authentication-local'
const app = feathers()
// Configure authentication
app.set('authentication', {
secret: 'your-secret-key',
authStrategies: ['jwt', 'local'],
service: 'users',
entity: 'user',
local: {
usernameField: 'email',
passwordField: 'password'
}
})
// User data resolver with password hashing
const userDataResolver = resolve({
properties: {
password: passwordHash({ strategy: 'local' })
}
})
// User dispatch resolver to exclude password
const userDispatchResolver = resolve({
properties: {
password: async () => undefined
}
})
// Setup users service
app.use('users', {
async create(data) {
// Store user logic
return { id: 1, ...data }
},
async get(id) {
// Get user logic
return { id, email: '[email protected]' }
},
async find(params) {
// Find users logic
return []
}
})
app.service('users').hooks({
before: {
create: [resolveData(userDataResolver)],
update: [resolveData(userDataResolver)],
patch: [resolveData(userDataResolver)]
},
after: {
all: [resolveDispatch(userDispatchResolver)]
}
})
// Setup authentication
const authService = new AuthenticationService(app)
authService.register('jwt', new JWTStrategy())
authService.register('local', new LocalStrategy())
app.use('authentication', authService)
Client Usage
import { feathers } from '@feathersjs/feathers'
import rest from '@feathersjs/rest-client'
import authClient from '@feathersjs/authentication-client'
const app = feathers()
app.configure(rest('http://localhost:3030').fetch(window.fetch))
app.configure(authClient())
// Register a new user
const user = await app.service('users').create({
email: '[email protected]',
password: 'secret123'
})
// Login
const result = await app.authenticate({
strategy: 'local',
email: '[email protected]',
password: 'secret123'
})
console.log(result.accessToken) // JWT token
console.log(result.user) // User object (without password)
Custom Local Strategy
Extend LocalStrategy for custom authentication logic:
import { LocalStrategy } from '@feathersjs/authentication-local'
import { NotAuthenticated } from '@feathersjs/errors'
class CustomLocalStrategy extends LocalStrategy {
async getEntityQuery(query, params) {
// Only authenticate active users
return {
...query,
$limit: 1,
isActive: true,
isVerified: true
}
}
async comparePassword(entity, password) {
// Add custom password validation
if (entity.failedLoginAttempts >= 5) {
throw new NotAuthenticated('Account locked due to too many failed attempts')
}
try {
return await super.comparePassword(entity, password)
} catch (error) {
// Track failed login attempts
await this.entityService.patch(entity.id, {
failedLoginAttempts: entity.failedLoginAttempts + 1
})
throw error
}
}
async authenticate(data, params) {
const result = await super.authenticate(data, params)
// Reset failed login attempts on success
await this.entityService.patch(result.user.id, {
failedLoginAttempts: 0,
lastLoginAt: new Date()
})
return result
}
}
// Register custom strategy
authService.register('local', new CustomLocalStrategy())