I would like to use JWT tokens for anonymous authentication in Angular. I only want to issue a new token to a user if there isn't already one in local storage. When I use the code below, a new token is issued every time the page is refreshed.
Any suggestions on how to prevent this?
Javascript (client side):
myApp.constant('API_URL', 'http://localhost:8080');
myApp.factory('UserFactory', function Userfactory($http, API_URL, AuthTokenFactory, $q) {
return $http.get(API_URL + '/api/authenticate').then(function success(response) {
AuthTokenFactory.setToken(response.data.token);console.log(response.data);
return response;
});
});
myApp.factory('AuthTokenFactory', function AuthTokenFactory($window) {
var store = $window.localStorage;
var key = 'auth-token';
return {
getToken: getToken,
setToken: setToken
};
function getToken() {
return store.getItem(key);
}
function setToken(token) {
if (token) {
store.setItem(key, token);
} else {
store.removeItem(key);
}
}
});
myApp.factory('AuthInterceptor', function AuthInterceptor(AuthTokenFactory) {
return {
request: addToken
};
function addToken(config) {
var token = AuthTokenFactory.getToken();
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = 'Bearer ' + token;
}
return config;
}
});
Related
I'm facing issue on refreshing expired JWT token based on 401 (unauthorized) header response. What i want is when user get 401 (header) response, than a new (refresh) JWT should generated by calling specific service (api).
I'm sending XSRF-TOKEN & access_token (JWT) in header response and these are working fine. I even also can get refresh (expired) token by calling api manually. But can't get it worked with 401 (header) response.
I've a factory that take care of this promise and intercepts header requests. My (factory) code looks like this.
angular.module('myApp').factory('httpRequestInterceptor', httpRequestInterceptor);
function httpRequestInterceptor($cookies, $rootScope, $q, $location, $injector) {
var replays = [];
var refreshTokenPromise;
var factory = {
request: request,
responseError: responseError
};
return factory;
//////////
function requestTodoWhenDone() {
var token = store.get('token');
return $http({
method: 'POST',
url: ApiEndpoint.url,
params: {
grant_type: 'refresh',
id_token: $cookies.get('access_token')
}
})
.success(function(response) {
// Set the refreshed token.
$cookies.put('access_token', response.data.access_token);
})
.then(function(){
// Attempt to retry the request if request config is passed.
if( !angular.isUndefined(requestTodoWhenDone) && requestTodoWhenDone.length > 0 ) {
// Set the new token for the authorization header.
requestTodoWhenDone.headers = {
'Authorization': 'Bearer ' + $cookies.get('access_token')
};
// Run the request again.
return $http(requestTodoWhenDone);
}
});
}
//////////
// Add authorization token to headers
function request(config) {
config.headers = config.headers || {};
if ($cookies.get('access_token')) {
config.headers.Authorization = 'Bearer ' + $cookies.get('access_token');
}
return config;
}
// Intercept 401s and redirect you to login
function responseError(response, requestTodoWhenDone) {
if (response.status === 401 && $cookies.get('access_token')) {
return checkAuthorization(response);
}
return $q.reject(response);
/////////
function checkAuthorization(res) {
return $q(function(resolve, reject) {
var replay = {
success: function(){
$injector.get('$http')(res.config).then(resolve, reject);
},
cancel: function(){
reject(res);
}
};
replays.push(replay);
console.log(replays);
if (!refreshTokenPromise) {
refreshTokenPromise = $injector.get('requestTodoWhenDone') // REFRESH TOKEN HERE
.refreshToken()
.then(clearRefreshTokenPromise)
.then(replayRequests)
.catch(cancelRequestsAndRedirect);
}
});
////////////
function clearRefreshTokenPromise(auth) {
refreshTokenPromise = null;
return auth;
}
function replayRequests(auth) {
replays.forEach(function(replay) {
replay.success();
});
replays.length = 0;
return auth;
}
function cancelRequestsAndRedirect() {
refreshTokenPromise = null;
replays.forEach(function(replay) {
replay.cancel();
});
replays.length = 0;
$cookies.remove('token');
var $state = $injector.get('$state');
// SET YOUR LOGIN PAGE
$location.path('/login');
}
}
}
}
Based on above code I'm getting following error in console when token expires (401 response).
Console Error
Error: "[$injector:unpr] Unknown provider: requestTodoWhenDoneProvider <- requestTodoWhenDone
Any help on this would be highly appreciable.
Thanks.
Ok i ended up with different way that solves the issue. But i still can't be able to redirect user to login page when my token inactive time is also expires (this happens after jwt expires).
Here is the code.
authInterceptor.service.js
angular.module('someApp').factory('AuthorizationTokenService', AuthorizationTokenService);
AuthorizationTokenService.$inject = ['$q', '$injector', '$cookies'];
function AuthorizationTokenService($q, $injector, $cookies) {
// Local storage for token
var tokenVM = {
accessToken: null
};
// Subscribed listeners which will get notified when new Access Token is available
var subscribers = [];
// Promise for getting new Access Token from backend
var deferedRefreshAccessToken = null;
var service = {
getLocalAccessToken: getLocalAccessToken,
refreshAccessToken: refreshAccessToken,
isAccessTokenExpired: isAccessTokenExpired,
subscribe: subscribe
};
return service;
////////////////////////////////////
// Get the new Access Token from backend
function refreshAccessToken() {
// If already waiting for the Promise, return it.
if( deferedRefreshAccessToken ) {
return deferedRefreshAccessToken.promise
} else {
deferedRefreshAccessToken = $q.defer();
// Get $http service with $injector to avoid circular dependency
var http = $injector.get('$http');
http({
method: 'POST',
url: 'api_url',
params: {
grant_type: 'refresh',
id_token: $cookies.get('access_token')
}
})
.then(function mySucces(response) {
var data = response.data;
if( data ){
// Save new Access Token
$cookies.put('access_token', data.access_token);
if( $cookies.get('access_token') ) {
// Resolve Promise
deferedRefreshAccessToken.resolve(data.access_token);
// Notify all subscribers
notifySubscribersNewAccessToken(data.access_token);
deferedRefreshAccessToken = null;
}
}
}, function myError(error) {
deferedRefreshAccessToken.reject(error);
deferedRefreshAccessToken = null;
});
return deferedRefreshAccessToken.promise;
}
}
function getLocalAccessToken() {
// get accesstoken from storage - $cookies
if ( $cookies.get('access_token') ) {
var access_token = $cookies.get('access_token')
return access_token;
}
}
function isAccessTokenExpired() {
// Check if expiresAt is older then current Date
}
function saveToken(accessToken) {
// get accesstoken from storage - $cookies
var access_token = $cookies.put('access_token');
console.log('access_token ' + access_token);
return access_token;
}
// This function will call all listeners (callbacks) and notify them that new access token is available
// This is used to notify the web socket that new access token is available
function notifySubscribersNewAccessToken(accessToken) {
angular.forEach(subscribers, function(subscriber) {
subscriber(accessToken);
});
}
// Subscribe to this service. Be notifyed when access token is renewed
function subscribe(callback) {
subscribers.push(callback);
}
}
Than in config (app.js) I've following code which intercepts appropriate header(s) and refresh (request) api on 401 response.
Here is the config code
config.$inject = ['$stateProvider', '$urlRouterProvider', '$httpProvider'];
function config($stateProvider, $urlRouterProvider, $httpProvider) {
// Push httpRequestInterceptor
// $httpProvider.interceptors.push('httpRequestInterceptor');
//Intercept all http requests
$httpProvider.interceptors.push(['$injector', '$q', "AuthorizationTokenService", "$cookies", function ($injector, $q, AuthorizationTokenService, $cookies) {
var cachedRequest = null;
return {
request: function (config) {
//If request if for API attach Authorization header with Access Token
if (config.url.indexOf("api") != -1) {
// var accessToken = AuthorizationTokenService.getLocalAccessToken();
console.log('cookie ' + $cookies.get('access_token'));
config.headers.Authorization = 'Bearer ' + $cookies.get('access_token');
}
return config;
},
responseError: function (response) {
switch (response.status) {
// Detect if reponse error is 401 (Unauthorized)
case 401:
// Cache this request
var deferred = $q.defer();
if(!cachedRequest) {
// Cache request for renewing Access Token and wait for Promise
cachedRequest = AuthorizationTokenService.refreshAccessToken();
}
// When Promise is resolved, new Access Token is returend
cachedRequest.then(function(accessToken) {
cachedRequest = null;
if (accessToken) {
// Resend this request when Access Token is renewed
$injector.get("$http")(response.config).then(function(resp) {
// Resolve this request (successfully this time)
deferred.resolve(resp);
},function(resp) {
deferred.reject();
console.log('success: refresh token has expired');
});
} else {
// If any error occurs reject the Promise
console.log('error: refresh token has expired');
deferred.reject();
}
}, function(response) {
// If any error occurs reject the Promise
cachedRequest = null;
deferred.reject();
return;
});
return deferred.promise;
}
// If any error occurs reject the Promise
return $q.reject(response);
}
};
}]);
}
The code is working fine on 401 (response) case which happens when JWT expires. But its not redirecting me to login page (In this case I've added console in promise request in config instead of redirection code)
Please help on this, thanks...
I have an angularJS web application which uses the web api and jwt. I followed tutorial on the internet here > angularjs-jwt-auth everything is working fine when I login using credentials from my own api,returns token on console as it should.
But the issue comes when I try to register a new user, nothing happens and console is throwing me an error Failed to load resource: the server responded with a status of 401 (Unauthorized). When I use api from tutorial is working fine so am little bit lost, please help!!
My code
(function () {
function authInterceptor(API, auth) {
return {
// automatically attach Authorization header
request: function (config) {
config.headers = config.headers || {};
var token = auth.getToken();
if (config.url.indexOf(API) === 0 && token) {
config.headers.Authorization = 'Bearer ' + token;
}
return config;
},
response: function (res) {
if (res.config.url.indexOf(API) === 0 && res.data.token) {
auth.saveToken(res.data.token);
}
return res;
},
}
}
// Services
function authService($window) {
var srvc = this;
srvc.parseJwt = function (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace('-', '+').replace('_', '/');
return JSON.parse($window.atob(base64));
};
srvc.saveToken = function (token) {
$window.localStorage['jwtToken'] = token
};
srvc.logout = function (token) {
$window.localStorage.removeItem('jwtToken');
};
srvc.getToken = function () {
return $window.localStorage['jwtToken'];
};
srvc.saveUsername = function (username) {
$window.localStorage['username'] = username;
}
srvc.getUsername = function () {
return $window.localStorage['username'];
}
srvc.isAuthed = function () {
var token = srvc.getToken();
if (token) {
var params = srvc.parseJwt(token);
return Math.round(new Date().getTime() / 1000) <= params.exp;
} else {
return false;
}
}
}
function userService($http, API, auth) {
var srvc = this;
srvc.register = function (first_name, last_name, email, password, role, gender, phone_number) {
return $http.post(API + '/api/v1/users/', { // <-- Registration link here
first_name: first_name,
last_name: last_name,
email: email,
password: password,
role: role,
gender: gender,
phone_number: phone_number
});
}
srvc.login = function (username, password) {
return $http.post(API + '/api/v1/token/auth/', { // <-- Login link here
username: username,
password: password
});
};
return srvc;
}
// We won't touch anything in here
function MainCtrl(user, auth, $location, $state, $rootScope) {
var self = this;
function handleRequest(res) {
var token = res.data ? res.data.token : null;
if (token) {
$location.path('/portfolio');
console.log('Bearer:', token);
auth.saveUsername($scope.username);
$rootScope.username = auth.getUsername();
}
// self.message = res.data.message;
}
self.login = function () {
user.login(self.username, self.password)
.then(handleRequest, handleRequest)
}
self.register = function () {
user.register(self.first_name, self.last_name, self.username, self.email, self.password, self.role, self.gender, self.phone_number)
.then(handleRequest, handleRequest)
}
self.logout = function () {
auth.logout && auth.logout();
$location.path('/login');
}
self.isAuthed = function () {
return auth.isAuthed ? auth.isAuthed() : false
}
}
angular
.module('App', ['ui.router'])
.factory('authInterceptor', authInterceptor)
.service('user', userService)
.service('auth', authService)
.constant('API', 'link-to-my-api') // <-- API Link here
.config(function ($stateProvider, $urlRouterProvider, $httpProvider) {
Since JWT verification is working for the tutorial's API, the fault lies in your authentication server i.e. creation/verification of JWT token) and not in the snippet above (the client handling)
You are getting 401 due to an incorrect JWT token created or incorrect validation of the JWT token
A brief explaination of how JWT authenticates.
In the example, the user first signs into the authentication server using the authentication server’s login system.
The authentication server then creates the JWT and sends it to the user.
When the user makes API calls to the application, the user passes the JWT along with the API call.
In this setup, the application server would be configured to verify that the incoming JWT are created by the authentication server
Hello I want to use external API to gather all the current currency rate. My front is based on token and I am storing token in localForage which is nothing but async localStorage.
//this execute after every page refresh
$localForage.getItem('authorization')
.then(function(authData) {
if(authData) {
$scope.authentication.isAuth = true;
$http.defaults.headers.common.Authorization = 'Bearer ' + authData.token;
//set authentication variable to true and add token to every request after page refresh
}
}, function(){
console.log("error with getting authorization localForage after refresh");
}
);
//this execute after custom event emitted after success login response
$rootScope.$on('localForageUpdated', function(event){
$localForage.getItem('authorization')
.then(function(authData) {
if(authData) {
$http.defaults.headers.common.Authorization = 'Bearer ' + authData.token;
$scope.authentication.isAuth = true;
//set authentication variable to true and add token to every request after page refresh
} else {
$scope.authentication.isAuth = false;
}
}, function(){
console.log("error with getting authorization localForage on event");
}
);
});
So this basically add header with token before every backend request.
Unfortunately when I try to download all the current currency rate from external API I get following error:
Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.
That's due to fact I added header with my token. Can I somehow add an exception while seting $http.defaults.headers.common.Authorization = 'Bearer ' + authData.token;?
Here is my solution you can use it to inspire you.
I create au interceptor to add the authorization. In this interception you can put your exception logic base on your need in my case I base it on the url.
angular.module('yourAppName').factory('authInterceptor', function ($q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.localStorage.token
&& $window.localStorage.token !== undefined
&& $window.localStorage.token !== 'undefined') {
if(config.url.startsWith("xyz")){
delete config.headers.Authorization;
} else {
config.headers.Authorization = 'Bearer ' + $window.localStorage.token;
}
}
return config;
},
response: function (response) {
return response || $q.when(response);
},
// optional method
responseError: function (response) {
return $q.reject(response);
}
};
});
angular.module('rmsApp').config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
});
I have written an auth interceptor that adds auth token to the request and handles auth errors if the user is not logged in.
var storeApp = angular.module('storeApp');
storeApp.factory('authInterceptor', function ($q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
}
return config;
},
response: function (response) {
return response || $q.when(response);
},
responseError: function (response) {
if (response.status === 401 || response.data.error === 'token_not_provided') {
console.log('auth error');
}
return $q.reject(response);
}
};
});
storeApp.config(function ($httpProvider) {
$httpProvider.defaults.withCredentials = true;
$httpProvider.interceptors.push('authInterceptor');
});
The issue is the auth interceptor is added to every request, regardless the request requires authentication or not. What is the best way to create an auth interceptor that only intercepts when the route requires authentication?
You need the filter out the requests you want in the authInterceptor factory methods
['/whatever/1', '/whatever/2', '/whatever/3'].forEach(function(value){
if (response.config.url.startsWith(value)) {
// do something
}
})
return response;
I am creating and sending a UID on the server side to the client side when the user visits a web page. I would like to use that UID as the subfolder to store each project a particular user posts to the server. I'm trying to figure out how best to accomplish this. When I use the code below, I am unable to access the UID in the Projects factory from the UserFactory.
Javascript (Angular):
myApp.factory('UserFactory', function UserFactory($http, API_URL, AuthTokenFactory, $q) {
return $http.get(API_URL + '/api/authenticate').then(function success(response) {
AuthTokenFactory.setToken(response.data.token);
return response;
});
});
myApp.factory('AuthTokenFactory', function AuthTokenFactory($window) {
var store = $window.localStorage;
var key = 'auth-token';
return {
getToken: getToken,
setToken: setToken
};
function getToken() {
return store.getItem(key);
}
function setToken(token) {
if (token) {
store.setItem(key, token);
} else {
store.removeItem(key);
}
}
});
myApp.factory('Projects', function($http, API_URL, UserFactory, AuthTokenFactory) {
var uid = UserFactory.response.data.token
var Projects = {
};
Projects.get = function(id) {
return $http.get(API_URL + '/api/projects/' + uid + id);
};
Projects.create = function(userData) {
return $http.post(API_URL + '/api/projects/' + uid, userData).then(function error(response) {
var data = response.data;
});
};
return Projects;
});
Node
apiRouter.get('/authenticate', function(req, res) {
var uid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
var token = jwt.sign({
uid: uid
}, superSecret, {
expiresInMinutes: 1440 // expires in 24 hours
});
res.json({
success: true,
message: 'Enjoy your token!',
uid: uid,
token: token
});
});