Skip to main content

Overview

The SMTP Server library supports multiple authentication methods through SASL (Simple Authentication and Security Layer). Authentication is handled via the onAuth handler, which validates credentials and returns user information.

Supported Authentication Methods

The library supports the following SASL mechanisms out of the box:
  • PLAIN - Username and password in base64 (default)
  • LOGIN - Legacy username/password method (default)
  • CRAM-MD5 - Challenge-response authentication
  • XOAUTH2 - OAuth 2.0 token authentication
  • XCLIENT - Postfix proxy authentication

Configuring Authentication Methods

authMethods
Array<string>
default:"['LOGIN', 'PLAIN']"
Array of authentication methods to advertise and accept.
authMethods: ['PLAIN', 'LOGIN', 'CRAM-MD5']
const server = new SMTPServer({
  authMethods: ['PLAIN', 'LOGIN'] // Default
});

The onAuth Handler

The onAuth handler is called when a client attempts to authenticate. It receives an auth object containing credentials, the session object, and a callback.

Handler Signature

onAuth(auth, session, callback)

Auth Object Structure

The auth object structure varies by authentication method:
{
  method: 'PLAIN',
  username: '[email protected]',
  password: 'secret123'
}
{
  method: 'LOGIN',
  username: '[email protected]',
  password: 'secret123'
}
{
  method: 'CRAM-MD5',
  username: '[email protected]',
  challenge: '<random@hostname>',
  challengeResponse: 'a1b2c3d4e5f6...',
  validatePassword: (password) => boolean // Helper function
}
{
  method: 'XOAUTH2',
  username: '[email protected]',
  accessToken: 'ya29.a0AfH6SMB...'
}
{
  method: 'XCLIENT',
  username: '[email protected]',
  password: null
}

Callback Response

The callback expects either an error or a response object:
// Success
callback(null, {
  user: userData // Can be user ID, object, or any value
});

// Failure
callback(null, {
  message: 'Invalid credentials',
  responseCode: 535 // Optional SMTP response code
});

// Error
callback(new Error('Database connection failed'));

Implementation Examples

Basic Username/Password Authentication

const server = new SMTPServer({
  authMethods: ['PLAIN', 'LOGIN'],
  
  onAuth(auth, session, callback) {
    const username = 'testuser';
    const password = 'testpass';
    
    if (auth.username === username && auth.password === password) {
      return callback(null, { user: auth.username });
    }
    
    callback(new Error('Invalid credentials'));
  }
});

CRAM-MD5 Authentication

CRAM-MD5 uses challenge-response to avoid sending passwords in plain text:
const server = new SMTPServer({
  authMethods: ['PLAIN', 'LOGIN', 'CRAM-MD5'],
  
  async onAuth(auth, session, callback) {
    if (auth.method === 'CRAM-MD5') {
      try {
        // Fetch user from database
        const user = await db.users.findOne({
          email: auth.username
        });
        
        if (!user) {
          return callback(null, { message: 'User not found' });
        }
        
        // Validate challenge response using stored password
        if (auth.validatePassword(user.password)) {
          callback(null, {
            user: { id: user.id, email: user.email }
          });
        } else {
          callback(null, { message: 'Invalid credentials' });
        }
      } catch (err) {
        callback(err);
      }
    } else {
      // Handle PLAIN/LOGIN
      const valid = await validateCredentials(
        auth.username,
        auth.password
      );
      
      if (valid) {
        callback(null, { user: auth.username });
      } else {
        callback(null, { message: 'Invalid credentials' });
      }
    }
  }
});
For CRAM-MD5, you must store the original password (or a reversibly encrypted version) to validate the challenge response. Use auth.validatePassword(password) helper.

XOAUTH2 (OAuth 2.0) Authentication

XOAUTH2 allows authentication using OAuth 2.0 access tokens:
const server = new SMTPServer({
  authMethods: ['XOAUTH2'],
  
  async onAuth(auth, session, callback) {
    if (auth.method === 'XOAUTH2') {
      try {
        // Verify OAuth token with provider
        const tokenInfo = await verifyGoogleToken(auth.accessToken);
        
        if (tokenInfo.email === auth.username) {
          callback(null, {
            user: {
              email: tokenInfo.email,
              name: tokenInfo.name
            }
          });
        } else {
          // Return challenge for failed auth
          callback(null, {
            data: {
              status: '401',
              schemes: 'bearer',
              scope: 'https://mail.google.com/'
            }
          });
        }
      } catch (err) {
        callback(err);
      }
    }
  }
});

Method-Specific Authentication

Handle different authentication methods with different logic:
const server = new SMTPServer({
  authMethods: ['PLAIN', 'LOGIN', 'CRAM-MD5'],
  
  onAuth(auth, session, callback) {
    switch (auth.method) {
      case 'PLAIN':
      case 'LOGIN':
        // Validate username/password
        return validatePassword(auth.username, auth.password)
          .then(user => callback(null, { user }))
          .catch(err => callback(err));
      
      case 'CRAM-MD5':
        // Validate challenge response
        return validateCramMd5(auth.username, auth.validatePassword)
          .then(user => callback(null, { user }))
          .catch(err => callback(err));
      
      case 'XOAUTH2':
        // Validate OAuth token
        return validateOAuthToken(auth.username, auth.accessToken)
          .then(user => callback(null, { user }))
          .catch(err => callback(err));
      
      default:
        callback(new Error('Unsupported auth method'));
    }
  }
});

Authentication Security

Requiring TLS for Authentication

By default, the server requires TLS before allowing authentication to prevent credential theft:
const server = new SMTPServer({
  secure: false, // Start without TLS
  hideSTARTTLS: false, // Advertise STARTTLS
  allowInsecureAuth: false, // Require TLS before AUTH (default)
  
  key: fs.readFileSync('private.key'),
  cert: fs.readFileSync('server.crt')
});
Setting allowInsecureAuth: true allows credentials to be sent over unencrypted connections. Only use in development or secure internal networks.

Optional Authentication

By default, authentication is required before accepting mail. To make it optional:
const server = new SMTPServer({
  authOptional: true, // Allow mail without authentication
  
  onAuth(auth, session, callback) {
    // Validate credentials when provided
    callback(null, { user: auth.username });
  }
});

Rate Limiting Unauthenticated Commands

Prevent abuse by limiting commands before authentication:
const server = new SMTPServer({
  maxAllowedUnauthenticatedCommands: 10, // Default
  
  // Set to false to disable limit
  maxAllowedUnauthenticatedCommands: false
});

Accessing User Data

Once authenticated, the user data is available in the session:
const server = new SMTPServer({
  onAuth(auth, session, callback) {
    callback(null, {
      user: {
        id: 123,
        email: auth.username,
        quota: 1000000
      }
    });
  },
  
  onData(stream, session, callback) {
    // Access authenticated user
    console.log('User:', session.user);
    // { id: 123, email: '...', quota: 1000000 }
    
    // Check quota, store in user's mailbox, etc.
  }
});

Custom Error Messages

const server = new SMTPServer({
  authRequiredMessage: 'Please authenticate before sending mail',
  
  onAuth(auth, session, callback) {
    if (!valid) {
      return callback(null, {
        message: 'Invalid username or password',
        responseCode: 535
      });
    }
    
    callback(null, { user: userData });
  }
});

Testing Authentication

You can test authentication using telnet or openssl:
# Connect to server
telnet localhost 2525

# EHLO to see available auth methods
EHLO client.example.com

# PLAIN authentication (base64 encoded "\0username\0password")
AUTH PLAIN AHRlc3R1c2VyAHRlc3RwYXNz

# LOGIN authentication
AUTH LOGIN
dGVzdHVzZXI=        # base64(username)
dGVzdHBhc3M=        # base64(password)
For STARTTLS connections:
openssl s_client -starttls smtp -connect localhost:587

Build docs developers (and LLMs) love