'How to tell if a user is logged in with http only cookies and JWT in react (client-side)

So I'm trying to follow the security best practices and I'm sending my JWT token over my React app in a only-secure http-only cookie.

This works fine for requests but the major issue I find with this approach is, how can I tell if the user is logged-in on client-side if I can't check if the token exists? The only way I can think of is to make a simple http to a protected endpoint that just returns 200.

Any ideas? (not looking for code implementations)



Solution 1:[1]

The approach I would follow is to just assume the user is logged in, and make the desired request, which will send the httpOnly token automatically in the request headers.

The server side should then respond with 401 if the token is not present in the request, and you can then react in the client side accordingly.

Solution 2:[2]

Using an endpoint like /api/users/me

Server-side

Probably you don't only need to know if a user is already logged in but also who that user is. Therefore many APIs implement an endpoint like /api/users/me which authenticates the request via the sent cookie or authorization header (or however you've implemented your server to authenticate requests).

Then, if the request is successfully authenticated, it returns the current user. If the authentication fails, return a 401 Not Authorized (see Wikipedia for status codes).

The implementation could look like this:

// UsersController.ts

// [...]
initializeRoutes() {
    this.router.get('users/me', verifyAuthorization(UserRole.User), this.getMe);
}

async getMe(req: Request, res: Response) {
    // an AuthorizedRequest has the already verified JWT token added to it
    const { id } = (req as AuthorizedRequest).token;

    const user = await UserService.getUserById(id);
    if (!user) {
      throw new HttpError(404, 'user not found');
    }

    logger.info(`found user <${user.email}>`);
    res.json(user);
}
// [...]
// AuthorizationMiddleware.ts
export function verifyAuthorization(expectedRole: UserRole) {
  // the authorization middleware throws a 401 in case the JWT is invalid
  return async function (req: Request, res: Response, next: NextFunction) {
    const authorization = req.headers.authorization;
    if (!authorization?.startsWith('Bearer ')) {
      logger.error(`no authorization header found`);
      throw new HttpError(401, 'unauthorized');
    }

    const token = authorization.split(' ')[1];
    const decoded = AuthenticationService.verifyLoginToken(token);
    if (!decoded) {
      logger.warn(`token not verified`);
      throw new HttpError(401, 'unauthorized');
    }
    (req as AuthorizedRequest).token = decoded;

    const currentRole = UserRole[decoded.role] ?? 0;
    if (currentRole < expectedRole) {
      logger.warn(`user not authorized: ${UserRole[currentRole]} < ${UserRole[expectedRole]}`);
      throw new HttpError(403, 'unauthorized');
    }

    logger.debug(`user authorized: ${UserRole[currentRole]} >= ${UserRole[expectedRole]}`);
    next();
  };
}

Client-side

If the response code is 200 OK and contains the user data, store this data in-memory (or as alternative in the local storage, if it doesn't include sensitive information).

If the request fails, redirect to the login page (or however you want your application to behave in that case).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 callback
Solution 2 winklerrr