login from single device at a time, logout others MERN, JWT, Google login

Viewed 633

I am unable to understand what should I do to prevent multiple login of same user. I am using google login with firebase in my React Node App with JWT token. How to make user logged out if login from other browser or other device. Is there any library that handle this or any way I can achieve this? I have checked many links but thee is no specific way of doing this.

Middleware auth

const auth = async (req, res, next) => {
  try {
    const token = req.headers.authorization.split(" ")[1];
    let decodedData = verifyToken(token);
    req.userId = decodedData?.id;
    next();
  } catch (error) {
    console.log(error);
    throw new Error()
  }
};

This is how I am verfying the token

export const verifyIdToken = async (idToken) => {
    try {
        return await admin.auth().verifyIdToken(idToken);
    } catch (e) {
        console.log(e);
        throw new Error(e)
    }
}

Controller auth.js

export const signin = async (req, res) => {
  try {
    const code = req.body.code;
    
    let profile = await verifyIdToken(code);
    const newUser = {
      uid: profile.uid,
      name: profile.name,
      email: profile.email,
userBrowser: req.headers["user-agent"],
isLoggedIn: true
        
        };
if(newUser.isLoggedIn)
{
console.log("user is logged in wth browser");
}
else
{
isLoggedIn: true
};
    let user = await db.aggregate('USER', query);
    user = user[0];
    if (!user) {
      user = await db.create('USER', newUser);
    }
    const token = generateToken(user);
    res.status(200).json({ user, token });
  } catch (err) {
    console.log(err)
    res.status(500).json({ message: "Something went wrong" });
  }
};

export const refreshToken = async (req, res) => {
  try {
   
    let user = await db.aggregate('USER', query);
    user = user[0];
    const token = generateToken(user);
    res.status(200).json({ user, token });
  } catch (err) {
    console.log(err)
    res.status(500).json({ message: "Something went wrong" });
  }
};

I have added the code, i am able to save data in db but how to check about the isLoggedIn function and logout user from first browser if loggedin from other browser? please help

3 Answers
  1. Install Firebase Admin SDK to your server. Follow this link: https://firebase.google.com/docs/admin/setup?hl=en
  2. Create API can receive uid of user then revokes current sessions. See more: https://firebase.google.com/docs/auth/admin/manage-sessions?hl=en

getAuth()
  .revokeRefreshTokens(uid)
  .then(() => {
   return getAuth().getUser(uid);
    })
     .then((userRecord) => {
      return new Date(userRecord.tokensValidAfterTime).getTime() / 1000;
      })
       .then((timestamp) => {
        console.log(`Tokens revoked at: ${timestamp}`);
         });

Then other user will have invalid Token, so closely logout.

  1. Check idToken revocation: https://firebase.google.com/docs/auth/admin/manage-sessions?hl=en#detect_id_token_revocation_in_the_sdk

// Verify the ID token while checking if the token is revoked by passing
// checkRevoked true.
    let checkRevoked = true;
    getAuth()
      .verifyIdToken(idToken, checkRevoked)
      .then((payload) => {
        // Token is valid.
      })
      .catch((error) => {
        if (error.code == 'auth/id-token-revoked') {
          // Token has been revoked. Inform the user to reauthenticate or signOut() the user.
        } else {
          // Token is invalid.
        }
      });

I hope above infomation to help you.

On react page, in the main file: index.js or app.js (whichever is the starting point), write this code:

  const token = localStorage.getItem("token");
  const userEmail = localStorage.getItem("userEmail");

  useEffect(() => {
    //if token found, send it to backend. It also means that user is logged into the browser, that is why token is found
    if(token) {
      const response = await axios.post("tokenValidityCheckAPI", {userEmail, token: token || ""});
      if(!response.data.user) {
        // if no user details received, that means user token/details are invalid, remove user's token from localStorage and log him out.
        removeUserSession();
      }
      // if details received from backend, just keep the user flow as it is
    }
  },[]);

  export const removeUserSession = () => {
    localStorage.removeItem("token");
    localStorage.removeItem("userEmail");
    // remove all the details which are being stored
  }

On backend side, create a new API to check token validity - tokenValidityCheckAPI. Also, check for the token when the user tries to log in and take necessary steps.

  export const checkTokenValidity = async(req, res) => {
    const {token, userEmail} = req.body;
    const userDetails = await db.user.findOne({email:userEmail});
    if(!userDetails) { // no user found with given email
      return res.json({user:null});
    }
    if(token===userDetails.token) { // If both token are same, then user is valid
      return res.json({user:userDetails});
    } else { // if token does not match, set token of user in db to ""
      userDetails.token = "";
      await db.user.updateOne({email:userEmail}, userDetails);
      return res.json({user:null});
    }
  }

  // checking while log in
  export const logIn = async(req, res) => {
    ...previous code
    // code to check whether user is valid or not.
    const userDetails = await db.user.findOne({email:userEmail});
    if(userDetails.token) { 
      // if token is found, that means user is logged in from somewhere else. 
      // Generate a new token to log him out from the old browser next time
      const newToken = await generateNewToken();
      userDetails.token = newToken;
      await db.user.updateOne({email:userEmail}, userDetails);
      return res.json({user:userDetails});
    }
  }

There might be different ways of doing this but i think, this way will also work.

First adding two more field to your users database

isLogedIn: (which should be false initially)

And another field

userBrowser: ( set to null initially)

Then when authenticating the user, after checking for username and password, you also check whether isLogerIn is false or true, if its true you can return a message to the user. But if false you can set it to true after authentication. And you can you get brower info with

req.headers["user-agent"]

And set it to userBrowser after authenticating the user. So when authenticating you check for isLogedIn and if true, you can send a message like

error: user already logger in, with (the brower nane already in database: userBrowser)
Related