Create A Passwordless Authentication System
Tags: javascript, typescript, node
Published: 2023-03-21 17:33:51

There is a new way of building an authentication into your backend application. This approach eliminates the need for a password, giving rise to the term passwordless authentication. Passwordless authentication systems are becoming more popular because they offer several benefits over traditional passwords based systems. For example, they can provide a better user experience, reduce the risk of data breaches, and lower the cost of password management.

A passwordless authentication system can be implemented in a number of ways, including by employing biometric information, one-time codes, or public-private key cryptography. The best option will rely on the particular requirements of the system and the users. Each method has advantages and disadvantages. For this article we would use a one-time code, which will be sent to the users email.

First let's spin up our npm project, run `npm init --y`, then install our dependencies, for this app we would use `express`.

```bash
$ npm i express
```

let's create our app, and get our server runing.

```javascript
// index.ts
import express from 'express';

const app = express();

app.listen(3000, () => console.log(`server running on port 3000`))
```

Next we'll set up our database with [Mongoose](https://mongoosejs.com/), to do this first we have to install Mongoose.

```bash
$ npm i mongoose
```

We have to setup our mongoose schema and models, which we will do below;

```javascript
// user.schema.js

import { Schema } from 'mongoose';

export const UserSchema = new Schema({
  email: {
    type: String,
    required: [true, "Please provide your email"],
    unique: true
  },
  fistName: String,
  lastName: String,
  otp: number;
}, {
  timestamps: true
})
```

We have declared a userschema above, the user schema defines some basic properties whic a user should have, most importantly the email which we set to be required for all users, we also ensure that two users cannot have the same emails by ensuring that emails are unique. Let's create the user model to handle all of the logic.

```javascript
// user.model.js

import { UserSchema } from './user.schema';
import { model } from 'mongoose';

UserSchema.statics.createAccount = async function ({
  email, 
  firstName,
  lastName
}) {
  try {
    const user = await this.create({ 
      email,
      firstName,
      lastName
    })
    // Generate OTP for user
    const otp = Math.floor(Math.random() * 1000000);
    await user.updateOne({ otp });
    sendEmailSomehow(`Your otp is ${otp}`)
    return [user, null]
  } catch(error) {
    return [null, error.message]
  }
}

```

The snippet above creates a new user for us, we generate an otp which will serve as the one time pass code which we will use to verify the user, we use an email service to send the user an email, I will assume that you have a third party service that will handle email delivery.

```javascript

UserSchema.statics.login = async function (email) {
  try {
    const user = await this.findOne({ email });
    const otp = Math.floor(Math.random() * 1000000);
    await user.updateOne({ otp });
    sendEmailSomehow(`Your otp is ${otp}`);
    return [user, null]
  } catch (error) {
     return [null, error.message]
  }
}

```
This snippet above handles the login process, we check to find a user with an email that matches the one provided, if there is a user we generate an otp for the user then send it to their email. Let's write the function that will validate their otp.

```javascript

UserSchema.statics.verifyOTP = async function (otp, email) {
  try {
    const user = await this.findOne({ email, otp });
    return [user, null];
  } catch (error) {
    return [null, error.message]
  }
}

export const Users = await model('user', UserSchema);
```

Now we have a basic setup in place, we need to import the `Users` model inside our controllers which we will create below.

```javascript
// user.controller.js

import { Users } from 'user.model';

export const createAccount = async (req, res) => {
  const { firstName, lastName, email } = req.body;
  const [user, err] = await Users.createAccount({
    firstName,
    lastName,
    email,
  });
  if (err) res.status(400).json({ error: err })
  res.status(201).json({ user })
}

```

We have the first controller which handles user registration set up, below we set up the other controllers for login in and verifying our user token.

```javascript

export const login = async (req, res) => {
  const { email } = req.body;
  const [user, err] = await Users.login(email);
  if (err) res.status(400).json({ error: err });
  res.status(201).json({ user });
}

export const verifyOTP = async (req, res) => {
  const { otp } = req.body;
  const [user, err] = await Users.verifyOTP(otp);
  if (err) res.status(400).json({ error: err });
  res.status(201).json({ user });
}
```

We have created and exported our controller functions what we need to do is to map each controller function to a route, this will be done inside a route file.

```javascript
// router.js
import { createAccount, login, verifyOTP } from './user.controller';
import { Router } from 'express';

export const router = Router();

router.post('/register', createAccount);
router.post('/login', login);
router.post('/otp', verifyOTP);

```

Our application is pretty done what we need to do now is to import our router and apply it to our application.


```javascript
// index.js continued
import { router } from 'router';

app.use(router);

```

Kick start your application and you have a basic setup for a password less authentication system, would you like to see how we could do this but with TypeORM and MySQL as our database and fastify as our sever framework. See you in the next article