JWT not storing after fetch

Hello friends,

I’ve been away from coding for a little while and I was doing a youtube tutorial to jump start me back into back end development. The video was for setting up basic user authentication, and I am finding that while the fetch request to login works fine, I can’t store the JSON Web Token upon completion of the fetch request. The video was using local Storage to store the JWT (I understand this is a security risk) but overall I am just not understanding why the code for it is failing to execute. The fetch request is successful, but any code that it attached to the proceeding .then statement on the login page doesn’t run. Local storage is empty upon completion of the request. I would appreciate any help. Thank you :).

The code for my login page is

<!DOCTYPE html>
<html lang='en'>
  <head>
    <meta charset='UTF-8'/>
    <meta name="viewport" content="width=device-width, intitial-scale=1.0" />
    <title>Login Page</title>
  </head>
  <body>
    <h1>Login</h1>
    <form id='login' method='post' action='/api/login' >
      <input type='text' id='username' name='username' autocomplete='off' placeholder='Username'>
      <input type='Password' id='password' name='password' autocomplete='off' placeholder='Password'>
      <input type='submit' value='Submit Form' /> 
        </form>

    <script>
     
      async function loginUser(event) {
        event.preventDefault()
        const username = document.getElementById('username').value
        const password = document.getElementById('password').value

        const result = await fetch('/api/login', {
           method: 'POST',
           credentials: 'include',
           mode: 'cors',
           headers: {
             Accept: 'application/json',
             Content-Type: 'application/json'
           },
           body: JSON.stringify({
            username, 
            password
           })
        }).then((res) => 
        localStorage.setItem('token', res.data)) 

       
        
      }


      
 const form = document.getElementById('login')
      form.addEventListener('submit', loginUser)


    </script> 
  </body>
  
</html>

The code for my server.js with all the routes is

const express = require('express')
const path = require('path')
const bodyParser = require('body-parser')
const mongoose = require('mongoose')
const User = require('./model/user')
const bcrypt = require('bcryptjs')
const mongodb = require('mongodb')
const jwt = require('jsonwebtoken')

const cors = require('cors')

mongoose.connect(process.env['URI'], { useNewUrlParser: true, useUnifiedTopology: true}).catch(error => console.log(error));

mongoose.connection.on('error', err => {
  console.log(err);
})

const app = express()

app.use('/', express.static(path.join(__dirname, 'static')))


app.use(cors())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false })) 

app.post('/api/change-password', async (req, res) => {
  const { token, newpassword } = req.body
  console.log(token)
  try {
    
  const user = jwt.verify(token, process.env['JWT_SECRET'])
    
  const _id = user.id 
    
  const hashedPassword = bcrypt.hash(newpassword, 10)  
    
  await User.updateOne({ _id }, { $set: { password: hashedPassword } } )
    
  } catch (error) {
    console.log(error.message)
    return res.json({ status: 'error', error: 'write something'})
  }

  
  res.json({ status: 'ok' })
})

app.post('/api/login', async (req, res) => {

  const { username, password } = req.body;
  

  const user = await User.findOne({ username }).lean()

  if (!user) {
    return res.json({ status: 'error', error: 'Invalid username/password' })
  }

  if (await bcrypt.compare(password, user.password)) {

     const token = jwt.sign({ id: user._id, username: user.username }, process.env['JWT_SECRET']
      )

   // console.log('login token ' + token)
    return res.json({ status: 'ok', data: token })
 
  }

  res.json({ status: 'error', error: 'Invalid username/password' })   
})


app.post('/api/register', async (req, res) => {
  console.log(req.body)
  const { username, password: plainTextPassword } = req.body

  if (!username || typeof username !== 'string') { return res.json({ status: 'error', error: 'Invalid Username' })}

  if (!plainTextPassword || typeof plainTextPassword !== 'string') { return res.json({ status: 'error', error: 'Invalid Password' })}

  if (!plainTextPassword.length > 5) {
    return res.json({ status: 'error', error: 'Password is too short. Please use 6 characters.' })
  }

  const password = await bcrypt.hash(plainTextPassword, 10)

  
  try {
    const response = await User.create({
      username: username,
      password: password
    })
    
    console.log('User created successfully ' + response)
 } catch (error) {

    if (error.code === 11000) {
      //duplicate username
      console.log(error)
    res.json({ status: 'error', error: 'Username already in use' })
    }
    throw error
  } 
 await res.json({status: 'ok'})                                             }) 

app.listen('9999', () => {
  console.log('Server at 9999')
})

Hello!

You’re mixing the way in which you handle the response on the front end: you should use async or use method chaining (.then(() => {}).catch((e) => {}), but not both (at least not on the same statement):

const result = await fetch('/api/login', {
           method: 'POST',
           credentials: 'include',
           mode: 'cors',
           headers: {
             Accept: 'application/json',
             Content-Type: 'application/json'
           },
           body: JSON.stringify({
            username, 
            password
           })
        })
        if (result.ok) {
            const { data } = await res.json();
            localStorage.setItem('token', data)
        } else {
            // Show an error
        }
1 Like

Thank you for your response.
I made the changes you suggested, but the problem is persisting.

Ok then, you will have to debug the responses on the server and client.

Log the response being sent by server (node/express), just before it gets sent to the client, and also log the response received by the client (react), are they sending/receiving what it should? For instance, on the client, is the data.status equal to ok? You can also check what the server responds by looking at the Network tab on the developer tools

1 Like

Console logs on the server file are fine and console logs on the login page never display. The request and response seem to work alright. The status is 200 on the request and the status is ok.

Sorry, I just realized there was an error on my previous code. The correct one is:

const result = await fetch('/api/login', {
           method: 'POST',
           credentials: 'include',
           mode: 'cors',
           headers: {
             Accept: 'application/json',
             Content-Type: 'application/json'
           },
           body: JSON.stringify({
            username, 
            password
           })
        })
        if (result.ok) {
            const { data } = await result.json();
            localStorage.setItem('token', data)
        } else {
            // Show an error
        }
1 Like

I just got it working and the final fix was literally placing Accept and Content-Type in quotation marks.

As soon as I remove the quotation marks it quits working again. Maybe because of the dash in Content-Type?

Thank you so much for your help!!

1 Like

Absolutely! I didn’t see that. Whenever you use characters that are not letters or numbers for an object’s property name, you have to use quotes.

I’m glad you solved it by yourself :grin:.

Happy coding!

1 Like