Skip to content

Authentication

Configuration

Control which SASL methods clients may use:

ts
const server = new SMTPServer({
  authMethods: ["PLAIN", "LOGIN", "CRAM-MD5", "XOAUTH2"],
  allowInsecureAuth: false, // require TLS before AUTH (default)
  authOptional: false,      // require AUTH (default)
});
OptionTypeDefaultDescription
authMethodsstring[]["PLAIN", "LOGIN"]Allowed SASL methods
authOptionalbooleanfalseAllow unauthenticated sessions
allowInsecureAuthbooleanfalseAllow AUTH over plain TCP (no TLS)
authRequiredMessagestringCustom message for 530 response

The onAuth callback is called for every AUTH attempt. Call callback(null, { user }) to accept or callback(new Error("reason")) to reject.

PLAIN and LOGIN

Both methods deliver credentials in the same auth object:

ts
const server = new SMTPServer({
  authMethods: ["PLAIN", "LOGIN"],
  onAuth(auth, session, callback) {
    if (auth.method !== "PLAIN" && auth.method !== "LOGIN") {
      return callback(new Error("Unsupported method"));
    }
    if (auth.username === "user" && auth.password === "secret") {
      callback(null, { user: auth.username });
    } else {
      callback(new Error("Invalid credentials"));
    }
  },
});

auth fields for PLAIN/LOGIN:

FieldTypeDescription
method"PLAIN" | "LOGIN"Which method the client used
usernamestringDecoded username
passwordstringDecoded password

CRAM-MD5

CRAM-MD5 does not transmit the password. The server sends a challenge, the client responds with an HMAC-MD5 digest. Use auth.validatePassword() to verify:

ts
const server = new SMTPServer({
  authMethods: ["CRAM-MD5"],
  onAuth(auth, session, callback) {
    if (auth.method !== "CRAM-MD5") {
      return callback(new Error("Unsupported method"));
    }
    const storedPassword = lookupPassword(auth.username);
    if (auth.validatePassword(storedPassword)) {
      callback(null, { user: auth.username });
    } else {
      callback(new Error("Invalid credentials"));
    }
  },
});

auth fields for CRAM-MD5:

FieldTypeDescription
method"CRAM-MD5"
usernamestring
challengestringThe server-generated challenge string
challengeResponsestringThe raw response from the client
validatePassword(password)(string) => booleanReturns true if the password matches

XOAUTH2

XOAUTH2 is used with OAuth2 access tokens:

ts
const server = new SMTPServer({
  authMethods: ["XOAUTH2"],
  onAuth(auth, session, callback) {
    if (auth.method !== "XOAUTH2") {
      return callback(new Error("Unsupported method"));
    }
    verifyToken(auth.username, auth.accessToken)
      .then((user) => callback(null, { user }))
      .catch(() => {
        // Return data to trigger the XOAUTH2 re-challenge
        callback(new Error("Invalid token"), {
          data: { status: "401", schemes: "bearer", scope: "mail" },
        });
      });
  },
});

auth fields for XOAUTH2:

FieldTypeDescription
method"XOAUTH2"
usernamestring
accessTokenstringOAuth2 bearer token

When authentication fails, you can pass a data object in the response to trigger an XOAUTH2 error challenge back to the client.

Storing the authenticated user

Whatever you pass as user in the success response is available on session.user for the rest of the connection:

ts
callback(null, { user: { id: 42, email: "user@example.com" } });

// later in onData:
function onData(stream, session, callback) {
  console.log(session.user); // { id: 42, email: 'user@example.com' }
}

Released under the MIT License.