http-only cookie + token : double job?

Viewed 2109

Dears,

I'm trying to find how managing authentification on client side using the hhtp-only cookie sent by the server.

What I don't understand is that since the HTTP only cookie can't be accessed by the front end, how the front end knows that the user is (still) authenticated ?

So far, the only solution if found is to send to the client a token when the authentication succeed. And keep this token in a second cookie created by the client.

But it seems to me that I'm doing the same job twice.

1- managing the HTTP only cookie on server side, especially the expiration date 2- managing also on client side the expiration date of the second cookie.

How can avoid this ? I'd like to manage the authentification on client side based on the HTTP only server cookie. If there is a server cookie, then go on, else redirect to login page.

I'm using node/express on server side and react on client one. The session is stored in redis, both sides are HTTPS using certificates.

Thks

1 Answers

You don't need to store another cookie. I suppose you use token based authentication on your endpoint, eg. JWT. Then you think about this scenario:

  1. User send username/password to server.
  2. Check user credentials and if there are valid, create http-only cookie with the token
    const user = await getUser({ where: { email } });

    const valid = await bcrypt.compare(password, user.password);
    if (!valid) {
      throw new UserInputError('Form Arguments invalid', {
        invalidArgs: {
          'password': 'Invalid password!',
        },
      });
    }

    const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
    /
    res.cookie('token', token, {
      httpOnly: true,
      maxAge: 1000 * 60 * 60 * 24 * 365,
    });

  1. Write auth middlerware to put the userId onto the req for future requests to access
const jwt = require('jsonwebtoken');
const { AuthenticationError } = require('apollo-server');

module.exports = async function(req, res, next) {
  const { token } = req.cookies;

  if (token) {
    try {
      const { userId } = jwt.verify(token, process.env.APP_SECRET);

      if (!userId) return next();
      req.userId = userId;

    } catch (e) {
      console.log(e);
    }
  }

  next();
};
  1. Check on each request the userId. If there is no userId, user doesn't logged in
  if (!req.userId) {
     throw new AuthenticationError('Log in!');
   }
  1. If user's token is invalid/expired you will get AuthenticationError. Catch it and redirect to login page.
  2. If your UI depends on user status, you can create easy-to-use component (i am using React) to check it.

User Component:

import { Query } from 'react-apollo';
import gql from 'graphql-tag';
import PropTypes from 'prop-types';

const CURRENT_USER_QUERY = gql`
  query CURRENT_USER_QUERY {
    me {
      userId
      firstName
      lastName
      profilePictureUrl
    }
  }
`;

const User = props => (
  <Query {...props} query={CURRENT_USER_QUERY} fetchPolicy={'cache-first'}>
    {payload => props.children(payload)}
  </Query>
);

User.propTypes = {
  children: PropTypes.func.isRequired,
};

export default User;

If we get me object from server, you know, there is a logged in user, so you can render depends on user's status:

import { Link } from 'react-router-dom';
import React from 'react';

<User>
  {({ loading, error, data: { me } }) => {
    if (loading || error || !me) return (
      <Button component={Link} to={'/login'}>Login</Button>
    );
    if(me) return (
      <Button component={Link} to={'/dashboard'}>Go to dashboard</Button>
    )
  }}
</User>
Related