Airbnb clone, implement login

Join the 2022 Full-Stack Web Dev Bootcamp!


This post is part of a new series where we build a clone of Airbnb with Next.js. See the first post here.

Let’s now implement login!

It’s going to be very similar to the registration, except we call /api/auth/login, and we don’t have to handle the password confirmation. And contrary to registration, we fail if the user is not there yet.

Let’s first install the cookies package from npm:

npm install cookies

Now create a pages/api/auth/login.js file.

In there, we first receive the email and password fields from the request, and we see if we can find a user with that email. If not, we return an error message:

import { User, sequelize } from '../../../model.js'

export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }

  const { email, password } = req.body

  let user = await User.findOne({ where: { email } })

  if (!user) {
    res.end(JSON.stringify({ status: 'error', message: 'User does not exist' }))
    return
  }
}

If the user exists, we check if the password is valid:

const isPasswordValid = await user.isPasswordValid(password)

if (!isPasswordValid) {
  res.end(JSON.stringify({ status: 'error', message: 'Password not valid' }))
  return
}

If the password is valid, we check the session is not expired. If it’s expired, we generate a new session token and a new expiration date. If not, we just expand the expiration date of 30 days:

let sessionToken = null
const sessionExpiration = new Date()
sessionExpiration.setDate(sessionExpiration.getDate() + 30)

if (new Date(user.session_expiration) < new Date()) {
  sessionToken = randomString(255)
  User.update(
    {
      session_token: sessionToken,
      session_expiration: sessionExpiration
    },
    { where: { email } }
  )
} else {
  sessionToken = user.session_token
  User.update(
    {
      session_expiration: sessionExpiration
    },
    { where: { email } }
  )
}

Finally we create a cookie and store the session token in there, and we terminate the network request:

const cookies = new Cookies(req, res)
cookies.set('nextbnb_session', sessionToken, {
  httpOnly: true // true by default
})

res.end(JSON.stringify({ status: 'success', message: 'Logged in' }))

Here’s the full source code of login.js:

import { User, sequelize } from '../../../model.js'
import Cookies from 'cookies'

const randomString = (length) => {
  const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let result = ''
  for (let i = length; i > 0; --i) {
    result += chars[Math.floor(Math.random() * chars.length)]
  }
  return result
}

export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }

  const { email, password } = req.body

  let user = await User.findOne({ where: { email } })

  if (!user) {
    res.end(JSON.stringify({ status: 'error', message: 'User does not exist' }))
    return
  }
  const isPasswordValid = await user.isPasswordValid(password)

  if (!isPasswordValid) {
    res.end(JSON.stringify({ status: 'error', message: 'Password not valid' }))
    return
  }

  let sessionToken = null
  const sessionExpiration = new Date()
  sessionExpiration.setDate(sessionExpiration.getDate() + 30)

  if (new Date(user.session_expiration) < new Date()) {
    sessionToken = randomString(255)
    User.update(
      {
        session_token: sessionToken,
        session_expiration: sessionExpiration
      },
      { where: { email } }
    )
  } else {
    sessionToken = user.session_token
    User.update(
      {
        session_expiration: sessionExpiration
      },
      { where: { email } }
    )
  }

  const cookies = new Cookies(req, res)
  cookies.set('nextbnb_session', sessionToken, {
    httpOnly: true // true by default
  })

  res.end(JSON.stringify({ status: 'success', message: 'Logged in' }))
}

In the components/LoginModal.js file, we are going to to like we did for the registration form and use useState to create 2 state variables for the email and the password, and we’re going to use Axios to perform the network request to /api/auth/login when the form is submitted:

import { useState } from 'react'
import axios from 'axios'

export default function LoginModal(props) {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const submit = async () => {
    const response = await axios.post('/api/auth/login', {
      email,
      password
    })
    console.log(response)

    if (response.data.status === 'error') {
      alert(response.data.message)
    }
  }

  return (
    <>
      <h2>Log in</h2>
      <div>
        <form
          onSubmit={(event) => {
            submit()
            event.preventDefault()
          }}
        >
          <input
            id="email"
            type="email"
            placeholder="Email address"
            onChange={(event) => setEmail(event.target.value)}
          />
          <input
            id="password"
            type="password"
            placeholder="Password"
            onChange={(event) => setPassword(event.target.value)}
          />
          <button>Log in</button>
        </form>
      </div>
      <p>
        Don't have an account yet?{' '}
        <a href="javascript:;" onClick={() => props.showSignup()}>
          Sign up
        </a>
      </p>
    </>
  )
}

See the code on GitHub

Next part: Determine if we are logged in

Want to become a better Web Developer? Join the 2022 Web Development Bootcamp!

⭐️⭐️⭐️ Join the 2022 Web Development Bootcamp ⭐️⭐️⭐️