implementing refresh-tokens with angular and express-jwt

后端 未结 1 616
轮回少年
轮回少年 2020-12-13 07:39

I want to implement the Sliding expiration concept with json web tokens using angular, nodejs and express-jwt. I\'m a little confused on how to do this, and am struggling to

1条回答
  •  离开以前
    2020-12-13 08:13

    I managed to implement this scenario.

    What I've done...

    On the server:

    -Enable an API endpoint for signin. This endpoint will respond with the Json Web Token in the header. The client side has to catch it (with $http interceptors) and save it (I use local storage). The client will also manage the refreshed tokens sent by the server.

    -On every request to the server configure a middleware in express to validate the token. At first I tried express-jwt module but jsonwebtoken was the right one for me.

    For specific routes you may want to disable the middleware. In this case signin and signout.

    var jwtCheck = auth.verifyJWT;
    jwtCheck.unless = unless;
    app.use('/api', jwtCheck.unless({path: [
        '/api/auth/signin',
        '/api/auth/signout'
    ]}));
    

    -The middleware verifyJWT always responds with a token in the header. If the token needs to be refreshed a refreshed function is called.

    jwtLib is my own library where the code lives to create, refresh and fetch jwt tokens.

    function(req, res, next) {
        var newToken,
            token = jwtLib.fetch(req.headers);
    
        if(token) {
            jwt.verify(token, config.jwt.secret, {
                secret: config.jwt.secret
            }, function(err, decoded) {
                if(err) {
                    return res.status(401).send({
                        message: 'User token is not valid'
                    });
                }
                //Refresh: If the token needs to be refreshed gets the new refreshed token
                newToken = jwtLib.refreshToken(decoded);
                if(newToken) {
                    // Set the JWT refreshed token in http header
                    res.set('Authorization', 'Bearer ' + newToken);
                    next();
                } else {
                    res.set('Authorization', 'Bearer ' + token);
                    next();
                }
            });
        } else {
            return res.status(401).send({
                message: 'User token is not present'
            });
        }
    };
    

    -The refresh function (jwtLib). As argument needs a decoded token, see above that jsonwebtoken resolve a decoded when call to jwt.verify().

    If you create during signin a token with an expiration of 4 hours and have a refresh expiration of 1 h (1 * 60 * 60 = 3600 secs) that means that the token will be refreshed if the user has been inactive for 3 hours or more, but not for more than 4 hours, because the verify process would fail in this case (1 hour window of refreshing). This avoids generating a new token on each request, only if the token will expire in this time window.

    module.exports.refreshToken = function(decoded) {
        var token_exp,
            now,
            newToken;
    
        token_exp = decoded.exp;
        now = moment().unix().valueOf();
    
        if((token_exp - now) < config.jwt.TOKEN_REFRESH_EXPIRATION) {
            newToken = this.createToken(decoded.user);
            if(newToken) {
                return newToken;
            }
        } else {
           return null;
        }
    };
    

    On the client (Angularjs):

    -Enable a client side for login. This calls the server endpoint. I use Http Basic Authentication encoded with base64. You can use base64 angular module to encode the email:password Note that on success I do not store the token on the localStorage or Cookie. This will be managed by the http Interceptor.

    //Base64 encode Basic Authorization (email:password)
    $http.defaults.headers.common.Authorization = 'Basic ' + base64.encode(credentials.email + ':' + credentials.password);
    return $http.post('/api/auth/signin', {skipAuthorization: true});
    

    -Configure the http interceptors to send the token to the server on every request and store the token on the response. If a refreshed token is received this one must be stored.

    // Config HTTP Interceptors
    angular.module('auth').config(['$httpProvider',
        function($httpProvider) {
            // Set the httpProvider interceptor
            $httpProvider.interceptors.push(['$q', '$location', 'localStorageService', 'jwtHelper', '$injector',
                function($q, $location, localStorageService, jwtHelper, $injector) {
                    return {
                        request: function(config) {
                            var token = localStorageService.get('authToken');
                            config.headers = config.headers || {};
    
                            if (token && !jwtHelper.isTokenExpired(token)) {
                                config.headers.Authorization = 'Bearer ' + token;
                            }
                            return config;
                        },
                        requestError: function(rejection) {
                            return $q.reject(rejection);
                        },
                        response: function(response) {
                            //JWT Token: If the token is a valid JWT token, new or refreshed, save it in the localStorage
                            var Authentication = $injector.get('Authentication'),
                                storagedToken = localStorageService.get('authToken'),
                                receivedToken = response.headers('Authorization');
                            if(receivedToken) {
                                receivedToken = Authentication.fetchJwt(receivedToken);
                            }
                            if(receivedToken && !jwtHelper.isTokenExpired(receivedToken) && (storagedToken !== receivedToken)) {
    
                                //Save Auth token to local storage
                                localStorageService.set('authToken', receivedToken);
                            }
                            return response;
                        },
                        responseError: function(rejection) {
                            var Authentication = $injector.get('Authentication');
                            switch (rejection.status) {
                                case 401:
                                    // Deauthenticate the global user
                                    Authentication.signout();
                                    break;
                                case 403:
                                    // Add unauthorized behaviour
                                    break;
                            }
    
                            return $q.reject(rejection);
                        }
                    };
                }
            ]);
        }
    ]);
    

    0 讨论(0)
提交回复
热议问题