12

I'm creating a custom token on backend server while login and below is the code:

UserSchema.methods.generateAuthToken = function() {
 var user = this;
 var access = "auth";

 return firebaseAdmin.default
  .auth()
  .createCustomToken(user._id.toHexString())
  .then(function(token) {
    user.tokens = user.tokens.concat([{ access, token }]);
    return user.save().then(() => {
      return token;
    });
  });
};

and storing this token inside mongodb and sending it back to client via header named "x-auth" and then stored it inside cookies.

On client side, using this token to signin like below:

axios({
  url: "/api/users/login",
  method: "POST",
  data: {
    email: data.email,
    password: data.password
  }
}).then(res => {
  Cookies.set("x-auth", res.headers["x-auth"], { expires: 7 });
  return firebase
    .auth()
    .signInWithCustomToken(res.headers["x-auth"])
    .then(response => {
      console.log("CA", response);
      response
        .getIdToken()
        .then(idToken => {
          Cookies.set("idToken", idToken, { expires: 7 });
        })
        .catch(e => {});
      dispatch(
        login({
          token: Cookies.get("x-auth")
        })
      );
    });
});

Now in order to call an api to fetch all users, I'm sending these tokens, custom token and idToken back to server:

const authenticate = (req, res, next) => {
const token = req.header("x-auth");
const idToken = req.header("idToken");
User.findByToken(token, idToken)
.then(user => {
  if (!user) {
    return Promise.reject({
      message: "no user found with the associated token!"
    });
  }
  req.user = user;
  req.token = token;
  next();
})
.catch(e => {
  res.status(401).send(setErrorMessage(e.message));
});
};

And findByToken is as below:

UserSchema.statics.findByToken = function(token, idToken) {
var User = this;
return firebaseAdmin.default
.auth()
.verifyIdToken(idToken)
.then(function(decodedToken) {
  var uid = decodedToken.uid;
  return User.findOne({
    _id: uid,
    "tokens.access": "auth",
    "tokens.token": token
  });
})
.catch(function(e) {
  return Promise.reject(e);
});
};

Why do I have to send both these tokens for authorization, is there anything wrong with the concept here.

https://firebase.google.com/docs/auth/admin/verify-id-tokens

Warning: The ID token verification methods included in the Firebase Admin SDKs are meant to verify ID tokens that come from the client SDKs, not the custom tokens that you create with the Admin SDKs. See Auth tokens for more information.

Please clarify if I can verify the custom token instead of idToken to retrieve userId and match it up with DB, instead of using different tokens for one purpose or I'm doing something wrong here and custom token should not be stored inside DB, and there is some other approach to it.

And now after sometime when I try to fetch all users, it says:

Firebase ID token has expired. Get a fresh token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.

Community
  • 1
  • 1
Vipul
  • 655
  • 2
  • 6
  • 22

1 Answers1

13

When working with tokens, you need to differentiate each token and what it is used for, also each token has related properties.

Custom Tokens: These tokens will be generated in the server (as you are doing) and are used in the client side to authenticate the client. These tokens expire after one hour.

Session ID Token: (I'm calling it "Session ID Token" just to differentiate it) When the client autheticates with any of the authentication providers, the SDK will exchange the information for an ID token used for the session. The same applies for custom tokens, where the SDK exchanges the custom token for an ID Token. ID Tokens are also short live and will expire after one hour. When getting the ID Token, the SDK also receives a refresh Token which is used for refreshing the session ID Token. The ID token is used when doing authenticated requests to Firebase.

ID Token for verification: To get this token, you will need to call the function "getIDToken" (for Web). This function will return an ID Token that can be used only to verify requests coming from the client to your server. Similar to the other tokens, this one expires after one hour. When calling the getIDToken, the function will request a new one if the current is expired.

To verify the ID Token, you must use the mentioned token. If you try to use the session ID Token or the Custom token, you will get errors. The same applies if you try to use an expired ID Token.

All the calls and tasks to refresh the ID Tokens are handled by the SDKs behind the scenes.

It is not a good practice to store these tokens as they will expire after one hour, is better if you call the appropriate functions to get the latets tokens and pass the correct ID Token to be verified.

Lastly, you can find in these links the properties used for generating the custom token and the ID Token for verification.

Gerardo
  • 3,460
  • 1
  • 16
  • 18
  • 1
    I understood following things, custom token from backend is used to login to frontend which in turn will generate idToken and that can be used for further communication between client and server and each time, to pass the token, I should fetch it from getIDToken(), and it automatically will provide the active token or will generate the new one if the previous one is expired, and that token will contain the user_id which then can be used by server to allow access and there is no need to store custom token inside db and match it up on each request as what I'm doing right now. Is that correct? – Vipul Apr 22 '18 at 09:36
  • One thing more, frameworks like Loopback.js, stores all the access tokens in a separate collection, why do they have to store it in the first place. Something I was also doing. Any idea about it? – Vipul Apr 22 '18 at 10:19
  • If my answer helped you, please consider to select it as the solution. – Gerardo Apr 23 '18 at 01:09
  • We need to store tokens in DB so that we can invalidate the login by deleting the token. So in my case i.e with firebase, how would I be achieving the same functionality i.e invalidate the login session if I'm not storing the tokens in DB? – Vipul Apr 26 '18 at 13:26
  • 1
    @Vipul You can invalidate tokens by calling `revokeRefreshTokens(uid)`. And then to validate, call `verifyIdToken(idToken, true)`. The second parameter, called 'checkRevoked' makes an extra check to see if the token has been revoked. (This is an extra network roundtrip to FB servers, but seems best to do it almost everytime, as far as I can judge.) – trollkotze May 14 '18 at 09:56