DEV Community

Cover image for Building your own Authentication!
iAmGjert
iAmGjert

Posted on

Building your own Authentication!

What is authentication?

Authentication is the process of verifying that a user is who they are. Nowadays almost everyone is getting so used to clicking "Login with Google" or "Login with Facebook" etc. that it's becoming second nature. Most everyone has a Google or Facebook account so it makes it easier on the user to not have to go through another tedious account creation. Similarly, it makes it easier on the end developer because most of what they'd need from a user to create a basic account is provided by those services when you use them to authenticate users on your application. What happens, though, when your user doesn't have an account with one of these 3rd party services? Are you expecting all your end users who don't have a Google or Facebook account to sign up for that service, to now use your service? That's expecting a lot from an end user! Recently I looked into different ways of building your own authentication service for your application and it was a bit simpler to setup then I'd originally thought. Using a package called bcrypt, a password hashing package that was first developed back in the 90s, I was able to build a mock database of user information very quickly.

Setup

  1. To start, I made a basic express service using the npm init command to start my project and then npm i express bcrypt nodemon to install the necessary dependencies.
  2. Next, use command touch server.js to create your server file. At the top of server.js, import express and bcrypt.

  3. Create your express app and use the .json() middleware to parse incoming json.

  4. Create your database that will hold all of your user information. It will contain an array of user objects. Typically you'd use some type of database like mongo or sql to house this data, but for the sake of this example I will be using a local users array.

const port = 3000; // <--- run the app an any unused port
const app = express(); // <--- create express app
app.use(express.json()) // <--- middleware to parse incoming JSON
app.listen(port, ()=>{
  console.log(`Server listening 👂 http://localhost:${port}`)
})

const users = []; // <--- fake local database
Enter fullscreen mode Exit fullscreen mode

The hard part is done! Now, for the final part we need to ensure we have a script for you in your package.json file to run your server, and keep it running while we work!
If you've been following along you should have a "test" script in your package.json file. We're going to rename that script "start" and pass it a value of "nodemon server.js"

"scripts": {
    "start": "nodemon server.js"
  }
Enter fullscreen mode Exit fullscreen mode

Test your script by running npm start in the terminal.

Now, we just need to build out a few endpoints for your server to hit and test some information. Paste these functions into your server.js file under your database declaration:

app.get('/', (req, res) => {
  res.status(200).send('Your server is working!');
})

app.get('/users', (req, res) => {
  if (users.length > 0) {
    res.status(200).send(users);
  } else {
    res.status(418).send('No users currently.')
  }
})

app.post('/users', async (req, res) => {
  try {
    const salt = await bcrypt.genSalt(); // <-- value can be incremented for more encryption.
    const hashedPassword = await bcrypt.hash(req.body.password, salt); // <--can be modified to take in # of 'rounds' instead of salt (default 10)
    console.log(salt);
    console.log(hashedPassword);
    const user = {
      name: req.body.name,
      password: hashedPassword,
      email: req.body.email
    }
    users.push(user);
    res.status(201).send(user);
  } catch {
    res.status(500).send();
  }
})

app.post('/users/login', async (req, res) => {
  const user = users.find( user => user.name = req.body.name );
  if (user === undefined) {
    return res.status(400).send('Username not found. Please check your credentials.')
  }
  try {
    if (await bcrypt.compare(req.body.password, user.password)) { //passwords match
      res.send('You\'ve successfully logged-in.');
    } else  { //passwords don't match
      res.send('Login failed, please check your credentials.');
    }; // <-- compare is secure to avoid 'timing attacks' bcrypt handles for you
  } catch {
    res.status(500).send();
  }
Enter fullscreen mode Exit fullscreen mode

Now that we have these functions written, we have a few endpoints we can hit:

  • GET '/' Should return a status 200 and a message letting you know your server is working.

  • GET '/users' Should return a status of 200 and the entire array of users.

  • POST '/users' Should take in a user object with a name, password, and email property. This endpoint will salt and hash your password, as well as create a user in the users array and store the users information + salted/hashed password.

  • POST '/users/login Should also take in a name, password, and email address. This endpoint will first check if the name exists in the database, then if it does it will attempt to check if the password provided matches the de-hashed password stored in the users database. Will return status 400 and message that username doesn't exist if name isn't in the database. Will return message stating successful login on successful password/username match. Will return message stating login failed on unsuccessful password match.

I've included some comments in the code itself to try to help explain some of the hashing/seeding methods. For more information check this link

Oldest comments (1)

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

As a security consultant, I would recommend using an IDP instead of rolling your own Auth and storing any credentials at your applications level. Keeping up with exploits is a full time job and something an IDP can cover with a huge team