Phone Auth with Express and Node.js
Tags: node, javascript, typescript, beginners
Published: 2022-10-02 02:56:50

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.


![Screenshot 2022-09-22 at 5.13.24 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664675431123/DUGxSEKbb.png align="left")

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

1. Request OTP 
2. 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](https://github.com/Faizan-Pasha/express-phone-auth-example.git).