OAuth2 allows your application to store and use authentication tokens instead of actual login credentials. This is great for security as tokens or valid only for specific actions and can be easily revoked thus, once stolen, can’t to as much harm as actual account credentials. OAuth2 authentication in Nodemailer is mostly used with Gmail and G Suite (née Google Apps) even though there are other providers that support it as well.
Access Tokens needed for OAuth2 authentication are short lived so these need to be regenerated from time to time. Nodemailer is able to use both 3LO and 2LO to automatically regenerate the tokens but you can also handle all token specific yourself.
Managing OAuth2 app credentials is hard, so you could let EmailEngine handle these for you. Once you have registered an email account with EmailEngine, you can use EmailEngine as an SMTP gateway without any authentication. EmailEngine will handle everything regarding the actual credentials itself. Read more here.
This is the “normal” way of obtaining access tokens. Your application requests permissions from the client and gets a refresh token in return that can be used to generate new access tokens.
You can find an example of how to generate required tokens from EmailEngine’s documentation.
auth – is the authentication object
Normal SMTP transport (ie. not the pooled version) has a convenience method of using separate authentication for every message. This allows you to set up a transport with just clientId and clientSecret values and provide accessToken and refreshToken with the message options. See example 5.
Nodemailer also allows you to use service accounts to generate access tokens. In this case the required auth
options are a bit different from 3LO auth. You can find an example of how to generate service tokens from EmailEngine’s documentation.
auth – is the authentication object
If you do not want Nodemailer to create new access tokens then you can provide a custom token generation callback that is called every time a new token is needed for an user.
The registered function gets the following arguments:
transporter.set("oauth2_provision_cb", (user, renew, callback) => {
let accessToken = userTokens[user];
if (!accessToken) {
return callback(new Error("Unknown user"));
} else {
return callback(null, accessToken);
}
});
If you use refreshToken or service keys to generate new tokens from Nodemailer when accessToken is not present or expired then you can listen for the token updates by registering a ‘token’ event handler for the transporter object.
transporter.on("token", (token) => {
console.log("A new access token was generated");
console.log("User: %s", token.user);
console.log("Access Token: %s", token.accessToken);
console.log("Expires: %s", new Date(token.expires));
});
Use an existing Access Token. If the token is not accepted then message is not sent as there is no way to generate a new token.
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: "user@example.com",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
},
});
This example requests a new accessToken value from a custom OAuth2 handler. Nodemailer does not attempt to generate the token by itself.
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: "user@example.com",
},
});
transporter.set("oauth2_provision_cb", (user, renew, callback) => {
let accessToken = userTokens[user];
if (!accessToken) {
return callback(new Error("Unknown user"));
} else {
return callback(null, accessToken);
}
});
This example uses an existing Access Token. If the token is not accepted or current time is past the expires value, then refreshToken is used to automatically generate a new accessToken
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: "user@example.com",
clientId: "000000000000-xxx0.apps.googleusercontent.com",
clientSecret: "XxxxxXXxX0xxxxxxxx0XXxX0",
refreshToken: "1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
expires: 1484314697598,
},
});
This example uses an existing Access Token. If the token is not accepted or current time is past the expires value, then a new accessToken value is generated using provided service account.
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: "user@example.com",
serviceClient: "113600000000000000000",
privateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg...",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
expires: 1484314697598,
},
});
This example demonstrates how to authenticate every message separately. This is mostly useful if you provide an email application that sends mail for multiple users. Instead of creating a new transporter for every message, create it just once and provide dynamic details with the message options.
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
clientId: "000000000000-xxx.apps.googleusercontent.com",
clientSecret: "XxxxxXXxX0xxxxxxxx0XXxX0",
},
});
transporter.sendMail({
from: "sender@example.com",
to: "recipient@example.com",
subject: "Message",
text: "I hope this message gets through!",
auth: {
user: "user@example.com",
refreshToken: "1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx",
accessToken: "ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x",
expires: 1484314697598,
},
});
Or alternatively you can do the same with your own OAuth2 handler.
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
},
});
transporter.set("oauth2_provision_cb", (user, renew, callback) => {
let accessToken = userTokens[user];
if (!accessToken) {
return callback(new Error("Unknown user"));
} else {
return callback(null, accessToken);
}
});
transporter.sendMail({
from: "sender@example.com",
to: "recipient@example.com",
subject: "Message",
text: "I hope this message gets through!",
auth: {
user: "user@example.com",
},
});
https://mail.google.com/
, make sure your client has this scope set when requesting permissions for an user