Skip to contentSkip to navigationSkip to topbar
On this page

Authy Two-factor Authentication Node.js Quickstart


(warning)

Warning

As of November 2022, Twilio no longer provides support for Authy SMS/Voice-only customers. Customers who were also using Authy TOTP or Push prior to March 1, 2023 are still supported. The Authy API is now closed to new customers and will be fully deprecated in the future.

For new development, we encourage you to use the Verify v2 API.

Existing customers will not be impacted at this time until Authy API has reached End of Life. For more information about migration, see Migrating from Authy to Verify for SMS(link takes you to an external page).

Adding two-factor authentication to your application is the easiest way to increase security and trust in your product without unnecessarily burdening your users. This quickstart guides you through building a Node.js(link takes you to an external page), AngularJS(link takes you to an external page), and MongoDB(link takes you to an external page) application that restricts access to a URL. Four Authy API channels are demoed: SMS, Voice, Soft Tokens and Push Notifications.

Ready to protect your toy app's users from nefarious balaclava wearing hackers? Dive in!


Sign Into - or Sign Up For - a Twilio Account

sign-into---or-sign-up-for---a-twilio-account page anchor

Create a new Twilio account (you can sign up for a free Twilio trial), or sign into an existing Twilio account(link takes you to an external page).

Create a New Authy Application

create-a-new-authy-application page anchor

Once logged in, visit the Authy Console(link takes you to an external page). Click on the red 'Create New Application' (or big red plus ('+') if you already created one) to create a new Authy application then name it something memorable.

Authy create new application.

You'll automatically be transported to the Settings page next. Click the eyeball icon to reveal your Production API Key.

Account Security API Key.

Copy your Production API Key to a safe place, you will use it during application setup.


Install and Launch MongoDB

install-and-launch-mongodb page anchor

When a user registers with your application, a request is made to Twilio to add that user to your App, and a user_id is returned. In this demo, we'll store the returned user_id in a MongoDB database.

Instructions for installing MongoDB vary by platform. Follow the instructions you need to install locally.

After installing, launch MongoDB. For *NIX and OSX, you can run:

mongod

Setup Authy on Your Device

setup-authy-on-your-device page anchor

This two-factor authentication demos two channels which require an installed Authy app to test: Soft tokens and push authentications. While SMS and voice channels will work without the client, to try out all four authentication channels download and install the Authy app for Desktop or Mobile:


Clone and Setup the Application

clone-and-setup-the-application page anchor

Clone our Node.js repository locally(link takes you to an external page), then enter the directory. Install all of the necessary node modules:

npm install

Next, open the file .env.example. There, edit the ACCOUNT_SECURITY_API_KEY, pasting in the API Key from the above step (in the console), and save the file as .env.

Depending on your system, you need to set the environmental variables before you continue. On *NIX, you can run:

source .env

On Windows, depending on your shell, you will have to use SET.

Alternatively, you could use a package such as autoenv(link takes you to an external page) to load it at startup.

Add Your Application API Key

add-your-application-api-key page anchor

Enter the API Key from the Account Security console and optionally change the port.

1
export ACCOUNT_SECURITY_API_KEY='ENTER_SECRET_HERE'
2
export PORT=1337

Once you have added your API Key, you are ready to run! Launch Node with:

node .

If MongoDB is running and your API Key is correct, you should get a message your new app is running!


Try the Node.js Two-Factor Demo

try-the-nodejs-two-factor-demo page anchor

With your phone (optionally with the Authy client installed) nearby, open a new browser tab and navigate to http://localhost:1337/register/(link takes you to an external page)

Enter your information and invent a password, then hit 'Register'. Your information is passed to Twilio (you will be able to see your user immediately in the console(link takes you to an external page)), and the application is returned a user_id.

Now visit http://localhost:1337/login/(link takes you to an external page) and login. You'll be presented with a happy screen:

Token Verification Page.

If your phone has the Authy app installed, you can immediately enter a soft token from the client to Verify. Additionally, you can try a push authentication by pushing the labeled button.

If you do not have the Authy app installed, the SMS and voice channels will also work in providing a token. To try different channels, you can logout to start the process again.

Two-Factor Authentication Channels

two-factor-authentication-channels page anchor

Demonstrating SMS, Voice, and Push Notification Two-Factor channels. (Soft Tokens can be entered directly.)

1
var crypto = require('crypto');
2
var mongoose = require('mongoose');
3
var User = mongoose.model('User');
4
var config = require('../config.js');
5
var qs = require('qs');
6
var request = require('request');
7
var phoneReg = require('../lib/phone_verification')(config.API_KEY);
8
9
// https://github.com/seegno/authy-client
10
const Client = require('authy-client').Client;
11
const authy = new Client({key: config.API_KEY});
12
13
14
function hashPW(pwd) {
15
return crypto.createHash('sha256').update(pwd).digest('base64').toString();
16
}
17
18
/**
19
* Login a user
20
* @param req
21
* @param res
22
*/
23
exports.login = function (req, res) {
24
User.findOne({username: req.body.username})
25
.exec(function (err, user) {
26
if (!user) {
27
err = 'Username Not Found';
28
} else if (('password' in req.body) && (user.hashed_password !==
29
hashPW(req.body.password.toString()))) {
30
err = 'Wrong Password';
31
} else {
32
createSession(req, res, user);
33
}
34
35
if (err) {
36
res.status(500).json(err);
37
}
38
});
39
};
40
41
/**
42
* Logout a user
43
*
44
* @param req
45
* @param res
46
*/
47
exports.logout = function (req, res) {
48
req.session.destroy(function (err) {
49
if (err) {
50
console.log("Error Logging Out: ", err);
51
return next(err);
52
}
53
res.status(200).send();
54
});
55
};
56
57
/**
58
* Checks to see if the user is logged in and redirects appropriately
59
*
60
* @param req
61
* @param res
62
*/
63
exports.loggedIn = function (req, res) {
64
if (req.session.loggedIn && req.session.authy) {
65
res.status(200).json({url: "/protected"});
66
} else if (req.session.loggedIn && !req.session.authy) {
67
res.status(200).json({url: "/2fa"});
68
} else {
69
res.status(409).send();
70
}
71
};
72
73
/**
74
* Sign up a new user.
75
*
76
* @param req
77
* @param res
78
*/
79
exports.register = function (req, res) {
80
81
var username = req.body.username;
82
User.findOne({username: username}).exec(function (err, user) {
83
if (err) {
84
console.log('Rregistration Error', err);
85
res.status(500).json(err);
86
return;
87
}
88
if (user) {
89
res.status(409).json({err: "Username Already Registered"});
90
return;
91
}
92
93
user = new User({username: req.body.username});
94
95
user.set('hashed_password', hashPW(req.body.password));
96
user.set('email', req.body.email);
97
user.set('authyId', null);
98
user.save(function (err) {
99
if (err) {
100
console.log('Error Creating User', err);
101
res.status(500).json(err);
102
} else {
103
104
authy.registerUser({
105
countryCode: req.body.country_code,
106
email: req.body.email,
107
phone: req.body.phone_number
108
}, function (err, regRes) {
109
if (err) {
110
console.log('Error Registering User with Authy');
111
res.status(500).json(err);
112
return;
113
}
114
115
user.set('authyId', regRes.user.id);
116
117
// Save the AuthyID into the database then request an SMS
118
user.save(function (err) {
119
if (err) {
120
console.log('error saving user in authyId registration ', err);
121
res.session.error = err;
122
res.status(500).json(err);
123
} else {
124
createSession(req, res, user);
125
}
126
});
127
});
128
}
129
});
130
});
131
};
132
133
134
/**
135
* Check user login status. Redirect appropriately.
136
*
137
* @param req
138
* @param res
139
*/
140
exports.loggedIn = function (req, res) {
141
142
if (req.session.loggedIn && req.session.authy) {
143
res.status(200).json({url: "/protected"});
144
} else if (req.session.loggedIn && !req.session.authy) {
145
res.status(200).json({url: "/2fa"});
146
} else {
147
res.status(200).json({url: "/login"});
148
}
149
};
150
151
/**
152
* Request a OneCode via SMS
153
*
154
* @param req
155
* @param res
156
*/
157
exports.sms = function (req, res) {
158
var username = req.session.username;
159
User.findOne({username: username}).exec(function (err, user) {
160
console.log("Send SMS");
161
if (err) {
162
console.log('SendSMS', err);
163
res.status(500).json(err);
164
return;
165
}
166
167
/**
168
* If the user has the Authy app installed, it'll send a text
169
* to open the Authy app to the TOTP token for this particular app.
170
*
171
* Passing force: true forces an SMS send.
172
*/
173
authy.requestSms({authyId: user.authyId}, {force: true}, function (err, smsRes) {
174
if (err) {
175
console.log('ERROR requestSms', err);
176
res.status(500).json(err);
177
return;
178
}
179
console.log("requestSMS response: ", smsRes);
180
res.status(200).json(smsRes);
181
});
182
183
});
184
};
185
186
/**
187
* Request a OneCode via a voice call
188
*
189
* @param req
190
* @param res
191
*/
192
exports.voice = function (req, res) {
193
var username = req.session.username;
194
User.findOne({username: username}).exec(function (err, user) {
195
console.log("Send SMS");
196
if (err) {
197
console.log('ERROR SendSMS', err);
198
res.status(500).json(err);
199
return;
200
}
201
202
/**
203
* If the user has the Authy app installed, it'll send a text
204
* to open the Authy app to the TOTP token for this particular app.
205
*
206
* Passing force: true forces an voice call to be made
207
*/
208
authy.requestCall({authyId: user.authyId}, {force: true}, function (err, callRes) {
209
if (err) {
210
console.error('ERROR requestcall', err);
211
res.status(500).json(err);
212
return;
213
}
214
console.log("requestCall response: ", callRes);
215
res.status(200).json(callRes);
216
});
217
});
218
};
219
220
/**
221
* Verify an Authy Token
222
*
223
* @param req
224
* @param res
225
*/
226
exports.verify = function (req, res) {
227
var username = req.session.username;
228
User.findOne({username: username}).exec(function (err, user) {
229
console.log("Verify Token");
230
if (err) {
231
console.error('Verify Token User Error: ', err);
232
res.status(500).json(err);
233
}
234
authy.verifyToken({authyId: user.authyId, token: req.body.token}, function (err, tokenRes) {
235
if (err) {
236
console.log("Verify Token Error: ", err);
237
res.status(500).json(err);
238
return;
239
}
240
console.log("Verify Token Response: ", tokenRes);
241
if (tokenRes.success) {
242
req.session.authy = true;
243
}
244
res.status(200).json(tokenRes);
245
});
246
});
247
};
248
249
/**
250
* Create a OneTouch request.
251
* The front-end client will poll 12 times at a frequency of 5 seconds before terminating.
252
* If the status is changed to approved, it quit polling and process the user.
253
*
254
* @param req
255
* @param res
256
*/
257
exports.createonetouch = function (req, res) {
258
259
var username = req.session.username;
260
console.log("username: ", username);
261
User.findOne({username: username}).exec(function (err, user) {
262
if (err) {
263
console.error("Create OneTouch User Error: ", err);
264
res.status(500).json(err);
265
}
266
267
var request = {
268
authyId: user.authyId,
269
details: {
270
hidden: {
271
"test": "This is a"
272
},
273
visible: {
274
"Authy ID": user.authyId,
275
"Username": user.username,
276
"Location": 'San Francisco, CA',
277
"Reason": 'Demo by Authy'
278
}
279
},
280
message: 'Login requested for an Authy Demo account.'
281
};
282
283
authy.createApprovalRequest(request, {ttl: 120}, function (oneTouchErr, oneTouchRes) {
284
if (oneTouchErr) {
285
console.error("Create OneTouch Error: ", oneTouchErr);
286
res.status(500).json(oneTouchErr);
287
return;
288
}
289
console.log("OneTouch Response: ", oneTouchRes);
290
req.session.uuid = oneTouchRes.approval_request.uuid;
291
res.status(200).json(oneTouchRes)
292
});
293
294
});
295
};
296
297
/**
298
* Verify the OneTouch request callback via HMAC inspection.
299
*
300
* @url https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
301
* @url https://gist.github.com/josh-authy/72952c62521480f3dd710dcbad0d8c42
302
*
303
* @param req
304
* @return {Boolean}
305
*/
306
function verifyCallback(req) {
307
308
var apiKey = config.API_KEY;
309
310
var url = req.headers['x-forwarded-proto'] + "://" + req.hostname + req.url;
311
var method = req.method;
312
var params = req.body;
313
314
// Sort the params.
315
var sorted_params = qs.stringify(params).split("&").sort().join("&").replace(/%20/g, '+');
316
317
var nonce = req.headers["x-authy-signature-nonce"];
318
var data = nonce + "|" + method + "|" + url + "|" + sorted_params;
319
320
var computed_sig = crypto.createHmac('sha256', apiKey).update(data).digest('base64');
321
var sig = req.headers["x-authy-signature"];
322
323
return sig == computed_sig;
324
}
325
326
/**
327
* Poll for the OneTouch status. Return the response to the client.
328
* Set the user session 'authy' variable to true if authenticated.
329
*
330
* @param req
331
* @param res
332
*/
333
exports.checkonetouchstatus = function (req, res) {
334
335
var options = {
336
url: "https://api.authy.com/onetouch/json/approval_requests/" + req.session.uuid,
337
form: {
338
"api_key": config.API_KEY
339
},
340
headers: {},
341
qs: {
342
"api_key": config.API_KEY
343
},
344
json: true,
345
jar: false,
346
strictSSL: true
347
};
348
349
request.get(options, function (err, response) {
350
if (err) {
351
console.log("OneTouch Status Request Error: ", err);
352
res.status(500).json(err);
353
}
354
console.log("OneTouch Status Response: ", response);
355
if (response.body.approval_request.status === "approved") {
356
req.session.authy = true;
357
}
358
res.status(200).json(response);
359
});
360
};
361
362
/**
363
* Register a phone
364
*
365
* @param req
366
* @param res
367
*/
368
exports.requestPhoneVerification = function (req, res) {
369
var phone_number = req.body.phone_number;
370
var country_code = req.body.country_code;
371
var via = req.body.via;
372
373
console.log("body: ", req.body);
374
375
if (phone_number && country_code && via) {
376
phoneReg.requestPhoneVerification(phone_number, country_code, via, function (err, response) {
377
if (err) {
378
console.log('error creating phone reg request', err);
379
res.status(500).json(err);
380
} else {
381
console.log('Success register phone API call: ', response);
382
res.status(200).json(response);
383
}
384
});
385
} else {
386
console.log('Failed in Register Phone API Call', req.body);
387
res.status(500).json({error: "Missing fields"});
388
}
389
390
};
391
392
/**
393
* Confirm a phone registration token
394
*
395
* @param req
396
* @param res
397
*/
398
exports.verifyPhoneToken = function (req, res) {
399
var country_code = req.body.country_code;
400
var phone_number = req.body.phone_number;
401
var token = req.body.token;
402
403
if (phone_number && country_code && token) {
404
phoneReg.verifyPhoneToken(phone_number, country_code, token, function (err, response) {
405
if (err) {
406
console.log('error creating phone reg request', err);
407
res.status(500).json(err);
408
} else {
409
console.log('Confirm phone success confirming code: ', response);
410
if (response.success) {
411
req.session.ph_verified = true;
412
}
413
res.status(200).json(err);
414
}
415
416
});
417
} else {
418
console.log('Failed in Confirm Phone request body: ', req.body);
419
res.status(500).json({error: "Missing fields"});
420
}
421
};
422
423
/**
424
* Create the initial user session.
425
*
426
* @param req
427
* @param res
428
* @param user
429
*/
430
function createSession(req, res, user) {
431
req.session.regenerate(function () {
432
req.session.loggedIn = true;
433
req.session.user = user.id;
434
req.session.username = user.username;
435
req.session.msg = 'Authenticated as: ' + user.username;
436
req.session.authy = false;
437
req.session.ph_verified = false;
438
res.status(200).json();
439
});
440
}

And there you go, two-factor authentication is on and your Node.js app is protected!


Now that you are keeping the hackers out of this demo app using two-factor authentication, you can find all of the detailed descriptions for options and API calls in our Authy API Reference. If you're also building a registration flow, also check out our Twilio Verify product and the Verification Quickstart which uses this codebase.

For additional guides and tutorials on account security and other products, in Node.js and in our other languages, take a look at the Docs.

Need some help?

Terms of service

Copyright © 2024 Twilio Inc.