Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a JWT Refresh Token to generate a new Access Token

I'm using Node.js and Express to deal with JWT Auth. First of all, every time a user is created and verified, I'm storing a refresh token inside User collection:

const refreshToken = await jwt.sign({ userId: decoded.user }, process.env.JWT_Refresh_Key);
const user = await User.updateOne({ _id: mongoose.Types.ObjectId(decoded.user) }, { refresh_token: refreshToken, status: true });

A JWT Access Token is generated after a successful Login (expires after 15min):

const token = await jwt.sign(
                { email: user.email, userId: user._id, role: user.role },
                process.env.JWT_Key,
                { expiresIn: '15m' });
res.status(200).json({success: true, token: token});

Then access token is stored in localStorage to be handled by Angular Http Interceptor and auth methods. After 15min, the token will be invalid for handling requests, so I need to use the refresh token stored on the database.

Refresh method is being called on AuthService.ts:

export class AuthService {
  constructor(private http: HttpClient){}

  refreshToken(token: string) {
      this.http.post<{token: string, expiresIn: number, name: string}>(`${BACKEND_URL}/refreshtoken`,     {token: token})
        .subscribe((data) => {
          const expiresAt = moment().add(data.expiresIn, 'second');
          localStorage.setItem('token', data.token);
          localStorage.setItem('expiration', JSON.stringify(expiresAt.valueOf()));
        });
    }
}

//route
router.post('/refreshtoken', user.refreshToken);

//controller user.js
exports.refreshToken = async(req, res, next) => {
    // I need to store and call 'old_refreshtoken' where??
    const user = await User.findOne({ refresh_token: old_refreshtoken });
    if (user) {
        const newToken = await jwt.sign(
            { email: user.email, userId: user._id, role: user.role },
            process.env.JWT_Key,
            { expiresIn: '15m' });
        res.status(200).json({success: true, token: newToken, expiresIn: 900, name: user.name});
    } else {
        res.status(401).json({success: false, message: 'Autenticação falhou.'});
    }
};

How can I use my refresh token (database) to generate a new access token? I'm not sure how to store a refresh token on client-side (Angular) to compare to refresh token stored on the database.

like image 846
James Avatar asked Oct 02 '18 23:10

James


2 Answers

I'm assuming that you have your own back-end to handle the refresh token process. Please tell me if this is not the case

What I did to this process is to move all decoding and encoding to the back-end. But you have to make sure that you store the latest active refresh token in the back-end. Otherwise, someone could reuse old token to create access token.

  1. In the front-end store the expiry date. Then, everytime you make a request to the back-end, check if the expiry date is not exceeded (probably you want to take into account delays of the request e.g. 5 seconds before expiry). If it's expired, fire the refresh-token method.
  2. Create a refresh token endpoint in the back-end and send both access-token and refresh-token to it
  3. Decode the access-token and get your necessary data. Ignore expiry date in this decode function.
  4. Compare refresh-token with the latest refresh-token in the db. If it doesn't match, the user is not authorized. Otherwise, continue.
  5. Now if you want to reuse the old data, you don't need to query your database and just re-encode the access-token content to a new token. Otherwise, do your query and rebuild the access-token.

Hope this helps

like image 173
kkesley Avatar answered Oct 05 '22 05:10

kkesley


Firstly, the user controller where you have a refresh token method doesn't need the next parameter, because you aren't utilizing it. The next parameter work closely like a promise, It shines when it comes to middleware handler.

The middleware flow should be separated from the controller, depends on your application folder structure.

You can create a refresh token end-point same way you did before a successful login.

 refreshToken = (payload, secret) => {
  const token = jwt.sign(payload, secret, {
    expiresIn: '15m',
  });
  return token
 }

then validate the token by using jwt verify method jwt.verify(token, secret, handler).

exports.refreshToken = (req, res, next) => {
 const token = req.headers['x-access-token'] || req.headers.token;
 /* if token was not provided */
 if (!token) {
    return res.status(403).send({
        success: false,
        message: 'Not Authorized',
    });
 }
 /* verify token*/
 jwt.verify(token, secret, (error, decoded) => {
    if (error) {
        return res.status(401).send({
            success: false,
            message: 'Invalid token',
        });
    }
    req.decoded = decoded;
    next();
 });
}

In the front-end store the expiry date (token). Then, every time you make a request to the back-end, check if the token is still valid(expired) by using the jwt verify method. If it's expired, fire the refresh-token method.

  • You can get your decoded token in req parameter like req.decoded.
  • Store token on the client side using localStorage, localStorage.setItem(userToken, token) and get token by localStorage.getItem(userToken).
like image 24
Akolade Adesanmi Avatar answered Oct 05 '22 06:10

Akolade Adesanmi