MEAN: Loggin in by JWT - angularjs

[Q1] What advantage does an HTTP Interceptor provide on modifying the config.headers["Authorization"] (frontend AngularJS) to contain the value of token when I can verify the requests by checking the req.cookies object? (at the backend NodeJS)
I am trying to understand how JSON web tokens function. The demo application I have setup has a login functionality.
On GET '/login' I am able to produce a token, set a cookie with it.
On the frontend, I can access a JSON object containing the token.
I can view the cookie in the developer console.
Nodejs:
index.js - login route
router.post('/login', function(req, res, next) {
Authenticator.find(req.cookies.token, req.body, Heartbeat.common, function(err, warning, data){
if(err) {
res.status(404).send({token:false, warning: null, error:err});
} else if(warning){
res.status(200).send({token:true, warning: warning, error:null});
} else {
res.cookie('token', data, {maxAge: 3600000, httpOnly:true});
res.status(200).json({token:true, error: null});
}
});
});
Authenticator.ctrl.js - Authenticator.find()
find: function(token, user, heartbeat, callback) {
if(!token) {
Auth.findOne({email:user.email}, function(err, data){
if(err) {
console.log(err);
} else {
if(data) {
if(data.checkHash(user.password)) {
callback(null, null,TokenMaker.createToken(user.email, heartbeat));
} else {
callback(Errors.login.strict.MISMATCH, null, null);
}
} else {
callback(Errors.login.strict.NOT_REGISTERED, null, null);
}
}
});
} else {
callback(null, Errors.login.warning.ACTIVE_REFRESH, null);
}
},
Angular Controller
app.controller('userAccessCtrl', ['$scope', '$http', function ($scope, $http){
$scope.user = {
email: "someone#some.com",
password: "12345679"
};
$scope.error = {};
$scope.loginAccess = function(user) {
var submitReady = true;
var emailStatus = EmailValidator.email(user.email);
var passwordStatus = EmailValidator.password(user.password);
if(typeof emailStatus === "string") {
$scope.error.email = emailStatus;
submitReady = false;
}
if(typeof passwordStatus === "string") {
$scope.error.password = passwordStatus;
submitReady = false;
}
if(submitReady) {
$scope.error = {}
var data = $scope.user;
$scope.user = {};
$http.post('/login', data)
.then(function(success){
console.log(success);
},function(error){
console.log(error);
});
}
}
}]);
Console response:
{
"data": {
"token":true,
"error":null
},
"status":200,
"config":{
"method":"POST",
"transformRequest":[null],
"transformResponse":[null],
"url":"/login",
"data":{
"email":"someone#some.com",
"password":"12345679"
},
"headers":{
"Accept":"application/json, text/plain, */*",
"Content-Type":"application/json;charset=utf-8"
}
},
"statusText":"OK"
}

Actually it's a wrong to use cookies and JWT tokens.
JWT token is much better for authentication than cookies.
When you use token, your server doesn't need to store session in database of memory and this is a big advantage for your application - you can scale you application, add new servers without thinking about how to sync sessions between servers.
In short words, when you use JWT token your flow is next:
frontend (in you case it's an angular) sends login and password to /login route
backend checks credentials and sends back token (in request body, not in cookies)
frontend app saves token in local storage or session storage of browser
and you can write HTTP Interceptor which will intercepts all requests to backend and it will attach "Authorization" header to all requests, it looks like next:
Authorization: Bearer here-is-your-jwt-token
backend can check this authorization header and if it is correct (look at http://jwt.io to read how verification works) backend can serve you request.

Related

How can I refresh a user's session using JWT token

I'm fairly new to Angular and I'm trying to implement a mechanism for keeping active users logged in as long as they're active.
I have a token endpoint that issues a JWT token to a user
{
"access_token": "base64encodedandsignedstring",
"token_type": "bearer",
"expires_in": 299,
"refresh_token": "f87ae3bee04b4ca39af6f22a198274df",
"as:client_id": "mysite",
"userName": "me#email.com",
".issued": "Wed, 19 Apr 2017 20:15:58 GMT",
".expires": "Wed, 19 Apr 2017 20:20:58 GMT"
}
And another call that takes the refresh_token and uses it to generate a new access token. From the Api standpoint this should enable me to pass in the refresh_token and generate a new JWT with a new expires date.
I'm not 100% sure on how to wire up the Angular side to support this, my login function:
var _login = function (LoginData) {
var data = "grant_type=password&username=" + LoginData.UserName + "&password=" + LoginData.Password + "&client_id=4TierWeb";
var deferred = $q.defer();
$http.post(serviceBase + 'authToken', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) {
localStorageService.set('authorizationData', { token: response.data.access_token, userName: LoginData.userName, refreshToken: response.data.refresh_token, useRefreshTokens: true });
_authentication.isAuth = true;
_authentication.userName = LoginData.UserName;
deferred.resolve(response);
}, function (err, status) {
_logOut();
deferred.reject(err);
});
return deferred.promise;
};
My refresh function:
var _refreshToken = function () {
var deferred = $q.defer();
var authData = localStorageService.get('authorizationData');
if (authData) {
if (authData.useRefreshTokens) {
var data = "grant_type=refresh_token&refresh_token=" + authData.refreshToken + "&client_id=4TierWeb";
localStorageService.remove('authorizationData');
$http.post(serviceBase + 'authToken', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) {
localStorageService.set('authorizationData', { token: response.data.access_token, userName: response.data.userName, refreshToken: response.data.refresh_token, useRefreshTokens: true });
// response.headers.Authorization = 'Bearer ' + response.token;
deferred.resolve(response);
}, function (err, status) {
_logOut();
deferred.reject(err);
});
}
}
return deferred.promise;
};
And my interceptor:
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {
request: function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function (error) {
if (error.status === 401) {
$location.path('/login');
}
return $q.reject(error);
}
};
return authInterceptorServiceFactory;
}]);
My interceptor works great without the refresh mechanism in place as above, but when I add the refresh mechanism:
authService.RefreshToken();
config.headers.Authorization = 'Bearer ' + authData.token;
I'm able to pull down a new JWT but the next line doesn't seem to be working correctly anymore, I'm getting 401 on my landing page and there is no bearer token in the payload, what am I missing here?
Updated Interceptor:
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
//var promise = $q.reject(rejection);
if (rejection.status === 401) {
var authService = $injector.get('authService');
// refresh the token
authService.refreshToken().then(function() {
// retry the request
var $http = $injector.get('$http');
return $http(rejection.config);
});
}
return $q.reject(rejection);
}
};
}
]);
You need to wait for the refresh_token request to complete obtaining a new access token and then use the response to issue a new request.
Like: authService.refreshToken().then(doRequest())
Lets suppose that you have 2 functions inside authService:
function getAccessToken() { ...get access token like in login()... } - returning Promise
function refreshToken() { ...existing logic... } - returning Promise
Let us say that you will use jwt_decode(jwt) to decode the JWT token.
I think you can go two ways with your implementation:
1st way: get the token and immediately subscribe in order to refresh when expired
function getAccessToken() {
...
return $http(...)
.then(function(response) {
// ...correct credentials logic...
if(authService.refreshTimeout) {
$window.clearTimeout(authService.refreshTimeout);
}
// decode JWT token
const access_token_jwt_data = jwt_decode(response.data.access_token);
// myOffset is an offset you choose so you can refresh the token before expiry
const expirationDate = new Date(access_token_jwt_data * 1000 - myOffset);
// refresh the token when expired
authService.refreshTimeout = $window.setTimeout(function() {
authService.refreshToken();
});
return response.data;
})
.catch(function(error) {
// ...invalid credentials logic...
return $q.reject(error);
});
}
NOTE: You can use window instead of $window. I don't think that you actually need a new digest cycle at that moment. A new digest will be launched when $http request completes successfully or not.
NOTE: This means that you need to take care also of the case when you reload the page. Thus re-enabling the refresh timeout. So you can reuse the logic within getAccessToken() for subscribing to expiry date but this time you get the token from the localStorage. This means that you can refactor this logic into a new function called something like function subscribeToTokenExpiry(accessToken). So you can call this function in your authService constructor if there is an access token in your localStorage.
2nd way: refresh the token in your HTTP interceptor after receiving an error code from server.
You can refresh your token if your interceptor receives an error that match a token expiry case. This depends strongly on your back-end implementation so you may receive HTTP 401 or 400 or anything else and some custom error message or code. So you need to check with your back-end. Also check if they are consistent in returning the HTTP statuses and error codes. Some implementation details might change over time and framework developers might advice users to not rely on that specific implementation because is only for internal use. In that case you can leave only the HTTP status and omit the code, as you will have better chances of having the same in the future. But ask your back-end or the ones that created the framework.
NOTE: regarding Spring OAuth2 back-end implementation, find the details at the end of this answer.
Getting back to your code, your interceptor should look like:
app.factory('authInterceptorService',
['$q', '$location', 'localStorageService', 'authService', '$injector',
function ($q, $location, localStorageService, authService, $injector) {
var authInterceptorServiceFactory = {
request: function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function (response) {
let promise = $q.reject(response);
if (response.status === 401
&& response.data
&& response.data.error === 'invalid_token') {
// refresh the token
promise = authService.refreshToken().then(function () {
// retry the request
const $http = $injector.get('$http');
return $http(response.config);
});
}
return promise.catch(function () {
$location.path('/login');
return $q.reject(response);
});
}
};
return authInterceptorServiceFactory;
}]);
Spring Security OAuth2 back-end related:
I add this section for those curious about Spring Authorization Server implementation as Spring is a very popular framework in the Java world.
1) Expiry date
Regarding the expiry date, this is expressed in seconds. You will find the "exp" key inside your access_token and refresh_token after you JWT decode the string.
This is in seconds because you add the JwtAccessTokenConverter which uses DefaultAccessTokenConverter that does:
if (token.getExpiration() != null) {
response.put(EXP, token.getExpiration().getTime() / 1000);
}
JwtAccessTokenConverter is added when the Authorization Server is being configured:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// ...
endpoints.accessTokenConverter(jwtAccessTokenConverter)
// ...
}
}
2) Access token expired response
You might need to handle one or both of HTTP 400 and HTTP 401 statuses and rely on { "error": "invalid_token" }. But this depends strongly on how the back-end was implemented using Spring.
See the explanations bellow:
Regarding, the resource server configuration (the one to which we send the request to in order to get the resource we want), the flow is as follows:
OAuth2AuthenticationProcessingFilter servlet filter to get access token from request
OAuth2AuthenticationManager to parse token string
DefaultTokenServices to obtain the access token object.
OAuth2AuthenticationProcessingFilter try catch will delegate the exceptions to OAuth2AuthenticationEntryPoint which creates the response for the exception.
DefaultTokenServices is a ResourceServerTokenServices implementation.
There are two possible such implementations, one is this DefaultTokenServices and the other is RemoteTokenServices.
If we use DefaultTokenServices then the token will be checked on the resource server. This means that the resource server has knowledge of the key that signed the token in order to check the token validity. This approach means distributing the key to all parties that want such behavior.
If we use RemoteTokenServices then the token will be checked against /oauth/check_token endpoint handled by CheckTokenEndpoint.
On expiry CheckTokenEndpoint will create an InvalidTokenException with HTTP 400, that will converted by OAuth2ExceptionJackson2Serializer into HTTP 400 with data { "error": "invalid_token", "error_description": "Token has expired" }.
On the other hand DefaultTokenServices will create also a InvalidTokenException exception but with other message and without overriding the HTTP status thus being HTTP 401 in the end. So this will become HTTP 401 with data { "error": "invalid_token", "error_description": "Access token expired: myTokenValue" }.
Again this, HTTP 400 or HTTP 401, happens because InvalidTokenException is thrown in both cases DefaultTokenServices throws without overriding getHttpErrorCode() which is 401 but CheckTokenEndpoint overrides it with 400.
Note: I added a Github Issue in order to check if this behavior, 400 vs 401, is correct.
I've used this interceptor at a couple of occasions without any problems.
You can set it up to refresh the token silently and only throw an error (and navigate to the login screen) if the refresh fails. Hope this helps
Is it secure to use a refresh token in an Angular application ? I'am not sure...
The OIDC implicit flow (which is the flow used for SPA or mobile apps), there is no refresh token involved.

Angular Node - send token to server upon opening new browser window

The question is pretty self-explanatory: I have a token stored on my browser. When I log in, the server-side authentication middleware uses Passport to authenticate me. However, when I close and re-open my browser window the token is still there but Passport doesn't know who I am.
How can I retrieve the token from the client side to the server side?
Below is my code.
app.get('/main', ensureAuthenticated, function(req, res, next) {
// thingToSend gets sent the first time, I will modify it
// later so it doesn't resend token if token exists
var thingToSend = {
token: createSendToken(req.user, config.secret),
id: req.user._id,
username: req.user.username,
authorization: req.user.authorization
};
res.render('main.ejs', thingToSend);
});
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
} else {
res.redirect('/login_page');
}
}
You should add an request interceptor to your angular app and attach your auth token along with every request you send to the server.
Read more about request interceptors here: https://docs.angularjs.org/api/ng/service/$http
So the answer to my problem was to make a request interceptor which I injected in app.config. The request interceptor collects the token, and anytime after my initial session the request interceptor attaches the token to the request header:
app.factory('httpInterceptor', function($q, $store, $window) {
return {
request: function (config){
config.headers = config.headers || {};
if($store.get('token')){
var token = config.headers.Authorization = 'Bearer ' + $store.get('token');
}
return config;
},
responseError: function(response){
if(response.status === 401 || response.status === 403) {
$window.location.href = "http://localhost:3000/login";
}
return $q.reject(response);
}
};
});
It then uses function ensureAuthentication() to check either a) Passport.authentication upon initial login, or b) checks token authentication anytime after that in a custom function checkAuthentication().
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
} else {
var x = checkAuthentication(req, res, next);
if (x === true) {
return next();
} else {
res.redirect('/login_Page');
}
}
}
Anyone interested in seeing checkAuthentication() or my progress in troubleshooting a related issue regarding JSON web tokens: JWT not decoding "JWT malformed" - Node Angular

Ionic : no content / white screen using interceptors

I successfully managed to use interceptors (AngularJs ) in my Ionic application. Previous post .
While it is working perfectly well in the browser using "ionic serve".
There is no content loaded in the header title and content block ( "ion-content" ) using "ionic run android" (either emulating on genymotion or on my own phone). See screenshot below.
I'm pretty sure it comes from the interceptors i'm using, because before that, the app was working on any platforms. Also, as soon as i remove the interceptors it is working again. Here the code.
Note that i'm checking which url is called so i don't go into a circular dependency or checking useless url, only the calls to my api go through.
app.config(function($httpProvider){
$httpProvider.interceptors.push(['$location', '$injector', '$q', function($location, $injector, $q){
return {
'request' : function(config){
// intercept request
// carefull includes might not work while emulating
// use instead indexOf for that case
if(!config.url.includes('/oauth/v2/token') && config.url.includes('/api')){
// inject the service manually
var OauthService = $injector.get('OauthService');
var access_token = OauthService.token();
config.url = config.url+'?access_token='+access_token.key;
}
return config;
}
}
}]);
});
Any ideas what could give this error? (By the way console is showing no errors on browser).
UPDATE :
OauthService.js :
app.factory('OauthService', function($http, $localStorage) {
return {
token : function(){
// Store actual token
access_token = $localStorage.getObject('access_token');
// Store actual identity
identity_token = $localStorage.getObject('identity_token');
// IF no user logged
if(isObjectEmpty(identity_token)){
// IF access_token does NOT exist OR will expires soon
if( isObjectEmpty(access_token) || Date.now() > (access_token.expires_at - (600*1000)) ){
// Create an anonymous access_token
return $http
.get(domain+'/oauth/v2/token?client_id='+public_id+'&client_secret='+secret+'&grant_type=client_credentials')
.then(function (response) {
$localStorage.setObject('access_token', {
key: response.data.access_token,
type: 'anonymous',
expires_at: Date.now()+(response.data.expires_in*1000)
});
return response.data.access_token;
});
}
}
// IF user is logged
else{
// IF access_token does NOT exist OR will expires soon OR is anonymous
if( isObjectEmpty(access_token) || Date.now() > (access_token.expires_at - (600*1000)) || access_token.type == 'anonymous' ){
// Create an access_token with an identity
return $http
.get(domain+'/oauth/v2/token?client_id='+public_id+'&client_secret='+secret+'&api_key='+identity_token+'&grant_type=http://oauth2.dev/grants/api_key')
.then(function (response) {
$localStorage.setObject('access_token', {
key: response.data.access_token,
type: 'identity',
expires_at: Date.now()+(response.data.expires_in*1000)
});
return response.data.access_token;
});
}
}
return access_token.key;
}
};
})
Did you install cordova whitelist plugin ?
cordova plugin add cordova-plugin-whitelist
or if you want to save the reference to your config.xml file:
cordova plugin add cordova-plugin-whitelist --save
If you don't have that your device won't be able to access external resources.
You can find more info here.
UPDATE:
I've checked your previous answer.
The idea of the interceptor is to intercept calls to an external service insert some action in the pipeline.
I would change your interceptor:
$httpProvider.interceptors.push(['$location', '$injector', '$q', '$localStorage', function($location, $injector, $q, $localStorage){
return {
'request' : function(config) {
config.headers = config.headers || {};
access_token = $localStorage.getObject('access_token');
if (access_token) {
config.headers.Authorization = 'Bearer ' + access_token;
}
}
'response' : function(response){
if (response.status === 401) {
logger.debug("Response 401");
}
return response || $q.when(response);
}
'responseError' : function(rejection){
if (rejection.status === 401) {
var OauthService = $injector.get('OauthService');
var access_token = OauthService.token();
if (access_token === null)
{
return $q.reject(rejection);
}
// Append your access token to the previous request and re-submits.
rejection.config.headers['Authorization'] = 'Bearer ' + access_token;
return $injector.get('$http')(rejection.config);
}
// This is necessary to make a `responseError` interceptor a no-op.
return $q.reject(rejection);
}
}
}]);
If you look at the interceptor above it manages all the requests to an external resource (REST api) and appends a bearer token to the authorization header if needed.
The response does not do much as it is only there for logging purposes.
responseError is the place where you should intercept and check if your token as expired, fetch a new one and resubmit the request.
We check if the user is not authorized to the request:
if (rejection.status === 401) { ... }
If not we request a new access token. I guess your OauthService does that.
If we have a new access token:
var access_token = OauthService.token();
we can, again, append the access token to the request header:
rejection.config.headers['Authorization'] = 'Bearer ' + access_token;
and resubmit the previous request:
return $injector.get('$http')(rejection.config);
If you want to find out more about interceptors you can read these blogs.

Sign in Twitter OAuth without Sessions aka token_secret (AngularJS Fail)

This is an express route from angularjs satellizer example, implementing 3 legged OAuth with Twitter:
/*
|--------------------------------------------------------------------------
| Login with Twitter
|--------------------------------------------------------------------------
*/
app.get('/auth/twitter', function(req, res) {
var requestTokenUrl = 'https://api.twitter.com/oauth/request_token';
var accessTokenUrl = 'https://api.twitter.com/oauth/access_token';
var authenticateUrl = 'https://api.twitter.com/oauth/authenticate';
if (!req.query.oauth_token || !req.query.oauth_verifier) {
var requestTokenOauth = {
consumer_key: config.TWITTER_KEY,
consumer_secret: config.TWITTER_SECRET,
callback: config.TWITTER_CALLBACK
};
// Step 1. Obtain request token for the authorization popup.
request.post({ url: requestTokenUrl, oauth: requestTokenOauth }, function(err, response, body) {
var oauthToken = qs.parse(body);
var params = qs.stringify({ oauth_token: oauthToken.oauth_token });
// Step 2. Redirect to the authorization screen.
res.redirect(authenticateUrl + '?' + params);
});
} else {
var accessTokenOauth = {
consumer_key: config.TWITTER_KEY,
consumer_secret: config.TWITTER_SECRET,
token: req.query.oauth_token,
verifier: req.query.oauth_verifier
};
// Step 3. Exchange oauth token and oauth verifier for access token.
request.post({ url: accessTokenUrl, oauth: accessTokenOauth }, function(err, response, profile) {
profile = qs.parse(profile);
// Step 4a. Link user accounts.
if (req.headers.authorization) {
User.findOne({ twitter: profile.user_id }, function(err, existingUser) {
if (existingUser) {
return res.status(409).send({ message: 'There is already a Twitter account that belongs to you' });
}
var token = req.headers.authorization.split(' ')[1];
var payload = jwt.decode(token, config.TOKEN_SECRET);
User.findById(payload.sub, function(err, user) {
if (!user) {
return res.status(400).send({ message: 'User not found' });
}
user.twitter = profile.user_id;
user.displayName = user.displayName || profile.screen_name;
user.save(function(err) {
res.send({ token: createToken(user) });
});
});
});
} else {
// Step 4b. Create a new user account or return an existing one.
User.findOne({ twitter: profile.user_id }, function(err, existingUser) {
if (existingUser) {
var token = createToken(existingUser);
return res.send({ token: token });
}
var user = new User();
user.twitter = profile.user_id;
user.displayName = profile.screen_name;
user.save(function() {
var token = createToken(user);
res.send({ token: token });
});
});
}
});
}
});
The problem is Step 3:
var accessTokenOauth = {
consumer_key: config.TWITTER_KEY,
consumer_secret: config.TWITTER_SECRET,
token: req.query.oauth_token,
verifier: req.query.oauth_verifier
};
// Step 3. Exchange oauth token and oauth verifier for access token.
request.post({ url: accessTokenUrl, oauth: accessTokenOauth });
Because the node-request documentation describes Step 3 as:
// step 3
// after the user is redirected back to your server
var auth_data = qs.parse(body)
, oauth =
{ consumer_key: CONSUMER_KEY
, consumer_secret: CONSUMER_SECRET
, token: auth_data.oauth_token
, token_secret: req_data.oauth_token_secret
, verifier: auth_data.oauth_verifier
}
, url = 'https://api.twitter.com/oauth/access_token'
;
request.post({url:url, oauth:oauth}
The difference is, in the satellizer example, it doesn't pass token_secret to sign-in, but it should. So is this a mistake or what am I missing?
The real problem for me was, 3 legged twitter sign-in flow actually requires session on server side, but the satellizer example doesn't use any sessions, so I was wondering how this possible without sessions, but either it is not possible and satellizer example is wrong, or I don't understand something.

implementing refresh-tokens with angular and express-jwt

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 find any example of refresh tokens or and other material relating to sessions with these technologies/frameworks.
A few options I was thinking of were
Generating a new token with each request after the initial login
Keeping track of issued token on the server side along
But I'm honestly not sure, please help
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);
}
};
}
]);
}
]);

Resources