A simple web tutorial to make a Phone Auth API with express using node.
Sign In user with Phone Number and OTP without Firebase or AWS Amplify.
I'm using Twilio in this example you could use any SMS provider.
Dependencies needed
-
Twilio
call the API to send the OTP.
Crypto
To create a hash using sha265
for client identification.
- jsonwebtoken
To send a JWT Token
when the user is authenticated.
- bodyParser
To parse the request body
Installing the dependencies
npm install Twilio crypto jsonwebtoken bodyParser
Getting started
Import statements
let app = require('express')();
let crypto = require('crypto');
let jwt = require('jsonwebtoken');
let bodyParser = require('body-parser');
A fake database for demo purpose, Make sure to connect it to a real database later
var users = [
{
id: 1,
name: 'John Doe',
number: '+1234567890',
},
{
id: 2,
name: 'Bob Williams',
number: '1234567891',
},
];
Body parsing was bundled in express
by default from v-4.16.0
. since 2020 express
has removed default body parsing. We have to do it manually now.
let jsonParser = bodyParser.json();
Generating keys
Generate a JWT Key
and Hash key
to verify that the token sent by the client has not been tampered with.
There are a lot of ways to generate unique keys. I am using node
to generate unique keys.
node
require('crypto').randomBytes(64).toStrong('hex')
This command will generate a random string. Copy it and paste it into the .env.
Run this command again for the second key and paste it into the .env.
HASH_KEY=<YOUR_HASH_KEY_HERE>
JWT_KEY=<YOUR_JWT_KEY_HERE>
Setup Twilio
For the sake of this example, we’ll be using Twilio but the concepts and code examples remain true for all SMS providers out there.
Login to your Twilio console.
Copy the Account SID
, Auth Token
, and My Twilio phone number
from the dashboard.
Paste it in .env
TWILIO_ACCOUNT_SID=<YOUR_ACCOUNT_SID_HERE>
TWILIO_AUTH_TOKEN=<YOUR_AUTH_TOKEN_HERE>
TWILIO_NUMBER=<YOUR_PHONE_NUMBER_HERE>
Declare the .env variables
Declare the .env variables on top of the file to get the secret key from the .env variable
require('dotenv').config();
let hashKey = process.env.HASH_KEY;
let jwtKey = process.env.JWT_KEY;
var accountSid = process.env.TWILIO_ACCOUNT_SID;
var authToken = process.env.TWILIO_AUTH_TOKEN;
let twilioNumber = process.env.TWILIO_NUMBER;
Declare the Twilio
client your Account SID
, Auth Token
, and My Twilio phone number
.
var client = require('twilio')(<Auccount SID>, <Auth Token>);
Now we are ready to build the endpoints.
We have to build two endpoints
- Request OTP
- Verify OTP
1. Request OTP
Get the user number from the request body
let number = req.body.number;
OTP generation logic
It will return 4 digit OTP, If you want to increase the length increase a 0
of 1000
.
let otp = Math.floor(1000 + Math.random() * 9000);
Create a secret hash to verify the client in 2. Verify OTP
let ttl = 5 * 60 * 1000;
let expires = Date.now() + ttl;
let data = `${number}.${otp}.${expires}`;
let hash = crypto.createHmac('sha256', hashKey).update(data).digest('hex');
let secretHash = `${hash}.${expires}`;
Check whether the user number is already registered
var user = users.find((user) => user.number === number);
Handle the case if the user doesn't exist
if (!user) {
users.push({
id: users.length + 1,
name: '',
number: number,
});
}
Now the fun part. Sending the OTP through Twilio and secretHash
through response
.
Twilio might get some errors so it is an excellent practice to wrap it inside of try catch
. call the client
method from Twilio and send OTP through SMS.
The body
property takes the message you want to send the client.
from
property takes your Twilio phone number.
to
property takes the client's number.
if the message is sent successfully, send the secretHast
through response
. If Twilio
throughs error
send "Error sending OTP"
as response
try {
client.messages
.create({
body: `Dear customer,\n Your OTP is ${otp}. PLEASE DO NOT SHARE THIS OTP WITH ANYONE.`,
from: twilioNumber,
to: number,
})
.then(() => {
//Send the secret hash to the client
res.json(secretHash);
})
.catch((err) => {
//Handle the twilio error
res.status(500).send('Error sending OTP');
});
} catch (err) {
//Handle the error
console.log(err);
}
2. Verify OTP
Get number
, OTP
, secretHash
from the requesr body
let { number, otp, secretHash } = req.body;
Slice the hash and the expiry time from the secretHash sent to the client in 1. Request OTP
let [hashValue, expires] = secretHash.split('.');
Check if the has expired and handle the case.
let now = Date.now();
if (now > parseInt(expires))
return res.json({ error: 'Timeout. Please try again' });
Create a new hash using the user number, OTP, and the expiry time
let data = `${number}.${otp}.${expires}`;
let newCalculatedHash = crypto
.createHmac('sha256', hashKey)
.update(data)
.digest('hex');
Compare the new hash with the hash sent by the client. if they match then generate a JWT
with the user information from the database and add an expiry period.
if (newCalculatedHash === hashValue) {
var user = users.find((user) => user.number === number);
let payload = {
number: number,
name: user.name,
id: user._id,
};
//Create a JWT token
let token = jwt.sign(payload, jwtKey, { expiresIn: '1y' });
//Send the token to the client
return res.json(token);
} else {
//Handle the case when the hash does not match
return res.json({ error: 'Invalid OTP. Please try again' });
}
Once complete, we’re all ready to preview the project. Run the following command to run a local development server.
node index.js
Source code is available at Github.
Top comments (0)