Baileys uses WhatsApp’s multi-device authentication system. Your authentication state includes credentials and encryption keys that must be persisted between sessions.
Authentication State
The authentication state consists of two parts:
- Credentials (
creds) - Account registration info, identity keys, device info
- Signal Keys (
keys) - Encryption keys for Signal protocol sessions
interface AuthenticationState {
creds: AuthenticationCreds
keys: SignalKeyStore
}
Using Multi-File Auth State
Baileys provides useMultiFileAuthState to store authentication in a folder structure:
import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys'
const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')
const sock = makeWASocket({
auth: state
})
// Save credentials whenever they update
sock.ev.on('creds.update', saveCreds)
How It Works
Initial setup
On first run, useMultiFileAuthState creates the folder and initializes empty credentials.const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')
// Creates folder: auth_info_baileys/
Authentication
When you connect, WhatsApp sends credentials after you scan the QR code or enter pairing code.const sock = makeWASocket({ auth: state })
// QR code appears, user scans it
// Credentials are automatically saved
Save on update
The creds.update event fires whenever credentials change.sock.ev.on('creds.update', saveCreds)
// Automatically saves to: auth_info_baileys/creds.json
Restore session
On subsequent runs, credentials are loaded automatically.const { state } = await useMultiFileAuthState('auth_info_baileys')
const sock = makeWASocket({ auth: state })
// Connects without QR code!
File Structure
The useMultiFileAuthState function creates the following structure:
auth_info_baileys/
├── creds.json # Main credentials
├── app-state-sync-key-{id}.json # App state keys
├── sender-key-{id}.json # Group encryption keys
├── session-{id}.json # Signal protocol sessions
└── pre-key-{id}.json # Pre-keys for new sessions
Never commit authentication files to version control. Add your auth folder to .gitignore.
Signal Key Management
WhatsApp uses the Signal protocol for end-to-end encryption. Keys are automatically managed:
interface SignalKeyStore {
get: (type: string, ids: string[]) => Promise<Record<string, any>>
set: (data: any) => Promise<void>
}
Key Types
- Pre-keys - Used to establish new sessions
- Session keys - Active encryption sessions with contacts
- Sender keys - Group message encryption keys
- App state sync keys - Sync app state across devices
Keys are updated automatically when messages are sent/received. Always save keys when creds.update fires to prevent message delivery failures.
For better performance, use makeCacheableSignalKeyStore:
import { useMultiFileAuthState, makeCacheableSignalKeyStore } from '@whiskeysockets/baileys'
const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')
const sock = makeWASocket({
auth: {
creds: state.creds,
keys: makeCacheableSignalKeyStore(state.keys, logger)
}
})
sock.ev.on('creds.update', saveCreds)
Benefits of Caching
- Reduces disk I/O operations
- Faster message encryption/decryption
- Improves overall socket performance
- Recommended for production use
Custom Auth State Implementation
For production systems, implement a custom auth state using a database:
import { AuthenticationState, SignalDataTypeMap } from '@whiskeysockets/baileys'
const usePostgresAuthState = async (): Promise<AuthenticationState> => {
// Load creds from database
const creds = await db.query('SELECT * FROM auth_creds WHERE id = 1')
return {
creds: creds || initAuthCreds(),
keys: {
get: async (type, ids) => {
const data: Record<string, any> = {}
const results = await db.query(
'SELECT id, data FROM auth_keys WHERE type = $1 AND id = ANY($2)',
[type, ids]
)
for (const row of results) {
data[row.id] = JSON.parse(row.data)
}
return data
},
set: async (data) => {
for (const type in data) {
for (const id in data[type]) {
const value = data[type][id]
if (value) {
await db.query(
'INSERT INTO auth_keys (type, id, data) VALUES ($1, $2, $3) ON CONFLICT (type, id) DO UPDATE SET data = $3',
[type, id, JSON.stringify(value)]
)
} else {
await db.query(
'DELETE FROM auth_keys WHERE type = $1 AND id = $2',
[type, id]
)
}
}
}
}
}
}
}
// Usage
const state = await usePostgresAuthState()
const sock = makeWASocket({ auth: state })
When implementing custom auth state, you MUST handle the creds.update event and save credentials. Failing to do so will cause authentication failures and message delivery issues.
Session Persistence
Always persist authentication state:
import { useMultiFileAuthState, BufferJSON } from '@whiskeysockets/baileys'
const connectToWhatsApp = async () => {
const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')
const sock = makeWASocket({
auth: state
})
// CRITICAL: Save credentials on every update
sock.ev.on('creds.update', saveCreds)
sock.ev.on('connection.update', (update) => {
const { connection, lastDisconnect, qr } = update
if (qr) {
console.log('QR Code:', qr)
// Use qrcode-terminal to display in terminal if needed
}
if (connection === 'close') {
const shouldReconnect =
lastDisconnect?.error?.output?.statusCode !== DisconnectReason.loggedOut
if (shouldReconnect) {
connectToWhatsApp()
}
} else if (connection === 'open') {
console.log('Connected!')
}
})
}
connectToWhatsApp()
BufferJSON Utility
When serializing auth state to JSON, use the BufferJSON utility:
import { BufferJSON } from '@whiskeysockets/baileys'
import fs from 'fs'
// Save with BufferJSON
const saveData = (data: any, file: string) => {
fs.writeFileSync(
file,
JSON.stringify(data, BufferJSON.replacer)
)
}
// Load with BufferJSON
const loadData = (file: string) => {
const data = fs.readFileSync(file, 'utf-8')
return JSON.parse(data, BufferJSON.reviver)
}
BufferJSON ensures that Buffer objects in the auth state are correctly serialized and deserialized.
Multi-Device Support
Baileys supports WhatsApp’s multi-device feature:
- Each device has its own authentication state
- Keys are managed independently per device
- Sessions are synced across devices via app state
// Device 1
const { state: state1 } = await useMultiFileAuthState('device1_auth')
const sock1 = makeWASocket({ auth: state1 })
// Device 2
const { state: state2 } = await useMultiFileAuthState('device2_auth')
const sock2 = makeWASocket({ auth: state2 })
Security Best Practices
Authentication files contain sensitive encryption keys. Follow these security practices:
- Never share auth files - Each device should have unique credentials
- Secure file permissions - Restrict access to auth folder (chmod 700)
- Encrypt at rest - Use disk encryption for auth storage
- Exclude from backups - Don’t backup auth files to cloud services
- Rotate credentials - Re-authenticate periodically for sensitive applications
Checking Registration Status
Check if the device is registered:
const sock = makeWASocket({ auth: state })
if (!sock.authState.creds.registered) {
console.log('Not registered, need to authenticate')
// Show QR code or request pairing code
} else {
console.log('Already registered, will connect automatically')
}
Handling Auth Errors
sock.ev.on('connection.update', (update) => {
const { connection, lastDisconnect } = update
if (connection === 'close') {
const statusCode = lastDisconnect?.error?.output?.statusCode
if (statusCode === DisconnectReason.loggedOut) {
console.log('Logged out - delete auth files and re-authenticate')
// Delete auth folder and restart
} else if (statusCode === DisconnectReason.badSession) {
console.log('Bad session - delete auth files')
// Delete auth folder and restart
}
}
})