PirateApp
PirateApp

Reputation: 6220

How to log the user out automatically when the cookie expires in Nuxt?

I am using the official NuxtJS Auth Routes example to perform login using express-sessions, it works perfectly

app.js file

const express = require('express')
const session = require('express-session')
const app = express()

app.use(express.json())
app.use(express.urlencoded({ extended: true }))
// session middleware
app.use(
  session({
    secret: 'super-secret-key',
    resave: false,
    saveUninitialized: false,
    cookie: { maxAge: 60000 }
  })
)

// Create express router
const router = express.Router()

// Transform req & res to have the same API as express
// So we can use res.status() & res.json()
router.use((req, res, next) => {
  Object.setPrototypeOf(req, app.request)
  Object.setPrototypeOf(res, app.response)
  req.res = res
  res.req = req
  next()
})

// Add POST - /api/login
router.post('/login', (req, res) => {
  if (req.body.username === 'demo' && req.body.password === 'demo') {
    req.session.authUser = { username: 'demo' }
    return res.json({ username: 'demo' })
  }
  res.status(401).json({ message: 'Bad credentials' })
})

// Add POST - /api/logout
router.post('/logout', (req, res) => {
  delete req.session.authUser
  res.json({ ok: true })
})

app.use('/api', router)

module.exports = app

The problem is when the cookie has expired, the user is still logged in on the front end. If they access an endpoint, they get logged out though because I am guessing nuxtServerInit gets called which unsets the user

store/index.js

import axios from 'axios'

export const state = () => ({
  authUser: null
})

export const mutations = {
  SET_USER(state, user) {
    state.authUser = user
  }
}

export const actions = {
  // nuxtServerInit is called by Nuxt.js before server-rendering every page
  nuxtServerInit({ commit }, { req }) {
    if (req.session && req.session.authUser) {
      commit('SET_USER', req.session.authUser)
    }
  },
  async login({ commit }, { username, password }) {
    try {
      const { data } = await axios.post('/api/login', { username, password })
      commit('SET_USER', data)
    } catch (error) {
      if (error.response && error.response.status === 401) {
        throw new Error('Bad credentials')
      }
      throw error
    }
  },

  async logout({ commit }) {
    await axios.post('/api/logout')
    commit('SET_USER', null)
  }
}

Upvotes: 1

Views: 4854

Answers (3)

Mohamed Sohail
Mohamed Sohail

Reputation: 1867

You can register a service worker to asynchronously listen for changes in the cookie store (that too without continuous polling!) This is fairly new and support for some browsers may not be there. Here is an article on it.

Alternatively you can use the Push API to notify the front end of expiry and call the logOut method. Your server should be able to track expiry and send a push.

If they access an endpoint, they get logged out though because I am guessing nuxtServerInit gets called which unsets the user.

Honestly, this is pretty much the default behavior on majority of websites. Unless you are building a webapp with security first in mind.

Upvotes: 1

PirateApp
PirateApp

Reputation: 6220

Very close to the answer here. The idea is to set req.session.expires when the user posts to login route. The problem at the moment is inside the async login method, how do you get access to req.session?

This is the server/index.js file

// Add POST - /api/login
router.post('/login', (req, res) => {
  if (req.body.username === 'demo' && req.body.password === 'demo') {
    req.session.authUser = { username: 'demo' }
    req.session.expires = req.session.cookie.expires.getTime()
    return res.json({ username: 'demo' })
  }
  res.status(401).json({ message: 'Bad credentials' })
})

This is the store/index.js file

export const actions = {
  // nuxtServerInit is called by Nuxt.js before server-rendering every page
  nuxtServerInit({ commit }, { req }) {
    if (req.session && req.session.authUser) {
      commit('SET_USER', req.session.authUser)
    }
    if (req.session && req.session.expires) {
      commit('SET_EXPIRES', req.session.expires)
    }
  },
  // How do you get req.session.expires HERE???
  async login({ commit }, { username, password }) {
    try {
      const response = await this.$axios.post('/api/login', {
        username,
        password
      })
      console.log(response, response.request, response.request.session)
      commit('SET_USER', response.data)
    } catch (error) {
      if (error.response && error.response.status === 401) {
        throw new Error('Bad credentials')
      }
      throw error
    }
  },

  async logout({ commit }) {
    await this.$axios.post('/api/logout')
    commit('SET_USER', null)
    commit('SET_EXPIRES', null)
  }
}

Upvotes: 0

Arc
Arc

Reputation: 1078

Short of having a websocket wait for a logout notice, you won't be able to immediately log the user out upon expiration. That's why it is waiting until you attempt to navigate again. If you want to instantly log the user out, you can create a Websocket that waits for a signal from a server upon cookie expiration.

https://github.com/nuxt/nuxt.js/tree/dev/examples/with-sockets

However, this is a specific use case. Most websites simply wait for the user to attempt another action that requires their token to be checked for expiration and then takes appropriate measures.

A simplified version of this can be done by tracking the expiration time left on the front-end in the store. Then you can use computed property to check the constantly decrementing countdown of how much time the user has left. When this hits zero, you can run your mutations.

Upvotes: 1

Related Questions