I have my request interceptor like below :
var requestFactory = angular.module('queryParamsModule', [])
.factory('headerInterceptor', function($injector) {
return {
request: requestInterceptor
};
function requestInterceptor(req) {
var accessToken;
$injector.get('tokenService').accessToken().then(function(res) {
accessToken = res.access_token;
console.log(accessToken); >>>>> accessToken is populated fine!
req.headers = _.extend({
'Authorization': 'Bearer ' + accessToken
}, req.headers);
}, function(e) {
// error
});
return req;
}
})
.config(function($httpProvider) {
$httpProvider.interceptors.push('headerInterceptor');
});
return queryParamsFactory;
The issue here is, angular doesnt wait for the Authorization Header to be added in request and pushes the interceptor before adding Authorization header.
Is there a way I can make my header interceptor part of the promise which is resolved after accessToken promise is complete?
As the manual says,
request: interceptors get called with a http config object. The function is free to modify the config object or create a new one. The function needs to return the config object directly, or a promise containing the config or a new config object.
So it should be:
function requestInterceptor(req) {
return $injector.get('tokenService').accessToken().then(function(res) {
...
return req;
}, function(e) { ... });
}
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 am working on a AngularJS prototype for injecting custom header for all API request in the application. I am doing a get request to an external API.
I am getting response when i request through DHC client adding custom header. If i try through Angular i am getting error.
var portal = angular.module('portal',[]);
portal.controller('MainController', function($scope, $http) {
$scope.pageTitle = "All Country Names";
$http.get("https://restcountries.eu/rest/v2/all").then(function (response) {
// $http.get("welcome.html").then(function (response) {
$scope.responseData = response.data;
console.log(response.data);
});
});
portal.factory('httpRequestInterceptor', function () {
return {
request: function (config) {
config.headers['ServerSession'] = 'dfsdasdfasdfasdf';
config.headers['Cookie'] = 'asdfasdfa';
return config;
}
};
});
portal.config(function ($httpProvider) {
$httpProvider.defaults.headers.common['Access-Control-Allow-Headers'] = '*';
$httpProvider.interceptors.push('httpRequestInterceptor');
});
ServerSession and Cookie will be retrieved from service and appended to the header.
i referred link Request header field is not allowed by Access-Control-Allow-Headers with $http
I'm trying to implement JWT token in this project. For that I used Authorization header in the $resource, like as this.
When I login on UI state "A", after getting logged in, I put the token in the localStorage as
$localStorage.token = data.token;
When I go to UI state "B" of the page, it uses the following service and send a request with no token. But on refreshing the page, it sends the same request with the token.
angular.module('BlurAdmin')
.factory('valueService', ['Token','$localStorage','$resource', 'endpoint', function(Token,$localStorage, $resource, endpoint) {
return {
getValues: $resource(endpoint + '/admin/getvalues', null, {
'get': {
method: 'GET',
headers:{'Authorization':'Bearer '+$localStorage.token}
}
}),
}
}]);
I think the service stores the $localStorage.token value initially and uses that even when the state changes. But when the page is reloaded, it gets the $localStorage.token value again.
How do I force this service to get the $localStorage.token value everytime the UI state changes?
Thanks in Advance!
To have the resource compute the header value on each XHR GET operation, furnish a function instead of a value:
angular.module('BlurAdmin')
.factory('valueService', ['Token','$localStorage','$resource', 'endpoint', function(Token,$localStorage, $resource, endpoint) {
return {
getValues: $resource(endpoint + '/admin/getvalues', null, {
'get': {
method: 'GET',
//headers:{'Authorization':'Bearer '+$localStorage.token}
headers:
{'Authorization':
function () {
return 'Bearer '+$localStorage.token;
}
}
}
}),
}
}]);
When a value is furnished, the header value is computed when the resource is created. When a function is furnished, the header value is computed each time the resource get method is called.
headers – {Object} – Map of strings or functions which return strings representing HTTP headers to send to the server. If the return value of a function is null, the header will not be sent. Functions accept a config object as an argument.
-- AngularJS $http Service API Reference - Usage
Your problem is that the resource definition is provided at the time of creation (before you have saved a token). To avoid this behavior, simply create a wrapper function and parse your token into it.
angular.module('BlurAdmin')
.factory('valueService', ['Token','$localStorage','$resource', 'endpoint', function(Token,$localStorage, $resource, endpoint) {
return function (token) {
return $resource(endpoint + '/admin/getvalues', {}, {
get: {
method: 'GET',
headers:{'Authorization':'Bearer ' + token}
}
})
}
}]);
Call your factory function like:
valueService($localStorage.token).get(function (result) {
console.log(result);
}, function (error) {
console.log(result);
});
If you are using the header with many API calls, It is better to add it in a commom place rather than adding it with each API
Please refer : intercepter https://docs.angularjs.org/api/ng/service/$http
angular.module('utimf.services', ['ngResource', 'ng.deviceDetector'])
.factory('UtimfHttpIntercepter', UtimfHttpIntercepter)
UtimfHttpIntercepter.$inject = ['$q', '$localStorage'];
function UtimfHttpIntercepter($q, $localStorage) {
var authFactory = {};
var _request = function (config) {
config.headers = config.headers || {}; // change/add hearders
config.data = config.data || {}; // change/add post data
config.params = config.params || {}; //change/add querystring params
config.headers['Authorization'] = 'Bearer '+$localStorage.token; // New headers are added here
return config || $q.when(config);
}
var _requestError = function (rejection) {
// handle if there is a request error
return $q.reject(rejection);
}
var _response = function(response){
// handle your response
return response || $q.when(response);
}
var _responseError = function (rejection) {
// handle if there is a request error
return $q.reject(rejection);
}
authFactory.request = _request;
authFactory.requestError = _requestError;
authFactory.response = _response;
authFactory.responseError = _responseError;
return authFactory;
}
and add $httpProvider.interceptors.push('UtimfHttpIntercepter'); in your config
I am using http Interceptor to intercept each http request in my application.
But I am getting Circular dependency found: $http <- APIInterceptor <- $http <- $templateRequest <- $compile
here is my Service code:
mPosServices.factory('mosServiceFactory', function ($http, $rootScope, $cookies, $q) {
return{
refresh_token: function () {
var refreshToken = $http({
method: "get",
url: "myservice/oauth/token?grant_type=refresh_token&client_id=restapp&client_secret=restapp&refresh_token=" + $cookies.get('refresh_token'),
})
return refreshToken;
}
});
mPosServices.service('APIInterceptor', ['mosServiceFactory','$cookies',function (mosServiceFactory,$cookies) {
var service = this;
service.request = function (config) {
if (!$cookies.get('access_token')) { //if access_token cookie does not exist
mosServiceFactory.refresh_token().then(function (response) {
var date = new Date();
date.setTime(date.getTime() + (response.data.expiresIn * 1000));
$cookies.remove('access_token');
$cookies.put('access_token', response.data.value, {expires: date});
$cookies.put('refresh_token', response.data.refreshToken.value);
}); //call the refresh_token function first
}
return config;
};
service.responseError = function (response) {
return response;
};
}]);
and pushing it as:
$httpProvider.interceptors.push('APIInterceptor');
in config function.
Am I doing something wrong here?
Even I tried to inject $http manually using
$injector
, but getting same error.
Kindly Help me.
You indeed need to add use $injector to get mosServiceFactory instance inside of interceptor. But this is not all you need to do. You also need to make sure you don't fall into infinite request loop because interceptor also makes a request. What you can do is to check if current request is the one for token refresh and if so don't fire one more request, I'm checking the URL for this.
One more important thing to mention. You need to return promise object from interceptor which resolves to original request config. This way it guaranties that intercepted request will be reissued after token is retrieved.
All together will look like this:
mPosServices.service('APIInterceptor', ['$injector', '$cookies', function($injector, $cookies) {
var service = this;
service.request = function(config) {
if (!$cookies.get('access_token') && config.url.indexOf('myservice/oauth/token?grant_type=') === -1) {
return $injector.get('mosServiceFactory').refresh_token().then(function(response) {
var date = new Date();
date.setTime(date.getTime() + (response.data.expiresIn * 1000));
$cookies.remove('access_token');
$cookies.put('access_token', response.data.value, {
expires: date
});
$cookies.put('refresh_token', response.data.refreshToken.value);
}).then(function() {
return config; // <-- token is refreshed, reissue original request
});
}
return config;
};
service.responseError = function(response) {
return response;
};
}]);
Check the demo I was testing solution on to see how it recovers original request after token is loaded.
Demo: http://plnkr.co/edit/1Aey2PThZQ4Y8IRZuVOl?p=preview
Using ngResource in AngularJS 1.2rc(x), how do I get the status code now?
RestAPI.save({resource}, {data}, function( response, responseHeaders ) {
});
where RestAPI is my ngResource.
The response has the $promise object and the resource returned from the server but not a status anymore. The responseHeaders() function only has a status if the server injects the status code into the header object, but not the true returned status code. So some servers may serve it and some might not.
You can use the promiss callbacks then, catch and finally after the $resource call.
For example. If you want to catch an error after a call, you would do something like this:
RestAPI.save({resource}, {data}, callbackFunction).$promise.catch(function(response) {
//this will be fired upon error
if(response.status == 500) alert('Something baaad happend');
}).then(function() {
//this will be fired upon success
});
The response object will have status and the statusText properties. status being an integer status code and statusText the text. You'll also have the data property containing the server response.
edit: as suggested, it was response.status
You must add an interceptor inside your resource declaration. Like this:
var resource = $resource(url, {}, {
get: {
method: 'GET'
interceptor: {
response: function(response) {
var result = response.resource;
result.$status = response.status;
return result;
}
}
}
});
Usage:
resource.get(params, function(result) {
console.log(result.$status)
});
IMO status code should have been provided by default.
There is an issue for this https://github.com/angular/angular.js/issues/8341
For anyone using a newer version of Angular, looks like we've had access to the status code as a 3rd param to the transformResponse function since angular 1.3, but it was never documented properly in the $resource docs.
I agreed responseHeaders() function will only return response's header,but you can custom it and it's useful anyway.
1.
To solve you problem. With the following:($$service is my $resource instance.)
var serve = new $$service();
serve.id = "hello_wrongPath"; // wrong path,will return 404
serve.$get()
.then(function (data) {
console.log("~~~hi~~~");
console.log(data);
return data;
})
.catch(function (error) {
console.log("~~~error~~~");
console.log(error);
console.log(error.status); // --> 404
console.log(error.statusText); // --> "Not Found"
console.log(error.config.timeout); // --> 5000
console.log(error.config.method); // --> GET
console.log(error.config.url); // --> request url
console.log(error.headers("content-type"));// --> "text/plain"
return error.$promise;
})
.finally(function(data){
console.log("~~~finally~~~");
console.log(data); // --> undefined
});
In this way,u can only catch status,statusText,timeout,method,headers(same with responseHeaders) in ERROR response.
2.
If you want to see response details in success response,I used a interceptor like this:
ng.module("baseInterceptor", [])
.factory("baseInterceptor", ["$q", function ($q) {
return {
'request': function (config) {
console.info(config);
//set timeout for all request
config.timeout = 5000;
return config;
},
'requestError': function (rejection) {
console.info(rejection);
return $q.reject(rejection);
},
'response': function (response) {
console.log("~~interceptor response success~~");
console.log(response);
console.log(response.status);
console.log(response.config.url);
return response;
},
'responseError': function (rejection) {
console.log("~~interceptor response error~~");
console.log(rejection);
console.log(rejection.status);
return $q.reject(rejection);
}
};
}]);
and then add interceptor to module:
.config(["$httpProvider", function ($httpProvider) {
$httpProvider.interceptors.push("baseInterceptor");
}])
You can get response status like this:
$http.get(url).then(function(response){
console.log(response.status); //successful status like OK
}, function(response){
console.log(response.status); //error status like 400-Bad Request
})
I'm using AngularJS v1.5.6, and I do it like this (in my case I put the "getData" method inside a service):
function getData(url) {
return $q(function (resolve, reject) {
$http.get(url).then(success, error);
function success(response) {
resolve(response);
}
function error(err) {
reject(err);
}
});
}
then in the controller (for example), call that like this:
function sendGetRequest() {
var promise = service.getData("someUrlGetService");
promise.then(function(response) {
//do something with the response data
console.log(response.data);
}, function(response) {
//do something with the error
console.log('Error status: ' + response.status);
});
}
As documentation says, the response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
See https://docs.angularjs.org/api/ng/service/$http
Hope it helps!
I think the right answer is a combination of Bardiel's and Ara's answers.
After adding an interceptor inside your resource declaration. Like this:
var resource = $resource(url, {}, {
get: {
method: 'GET'
interceptor: {
response: function(response) {
var result = response.resource;
result.$status = response.status;
return result;
}
}
}
});
Use it as below:
RestAPI.save()
.query(function(response) {
// This will return status code from API like 200, 201 etc
console.log(response.$status);
})
.$promise.catch(function(response) {
// This will return status code from server side like 404, 500 etc
console.log(response.status);
});
I had faced the similar problem.I looked into the angular lib and added a few lines to have status returned in the response itself.In this file,find where promise is being returned.
Replace code block starting with
var promise = $http(httpConfig).then(function(response)
with the following
var promise = $http(httpConfig).then(function(response) {
var data = response.data,
promise = value.$promise;
if (data) {
// Need to convert action.isArray to boolean in case it is undefined
// jshint -W018
if ( angular.isArray(data) !== (!!action.isArray) ) {
throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' +
'response to contain an {0} but got an {1}',
action.isArray?'array':'object', angular.isArray(data)?'array':'object');
}
// jshint +W018
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
value.push(new Resource(item));
});
} else {
copy(data, value);
value.$promise = promise;
}
}
value.status = response.status;
value.$resolved = true;
response.resource = value;
return response;
}, function(response) {
value.status = response.status;
value.$resolved = true;
(error||noop)(response);
return $q.reject(response);
});
or you can add this line
value.status = response.status;
and then access status in code like reponse.status.Though,this is kind of hack but worked for me.I also had to make changes in the minified version.