I'm writing an angularjs client for a token based restful API. The tokens in the API expire every hour so every time the token is expired in my client there should be a refresh token action.
The controller which handles the API call results looks like this:
angular.module('apitestApp')
.controller('MainCtrl', ['$rootScope', '$scope', 'httpService', function ($rootScope, $scope, httpService) {
$scope.messages = [];
var url = $rootScope.domainPath + $rootScope.apiPath + 'messages.json';
httpService.getRequest(url, {}).then(
function (data){
$scope.messages = data;
}
);
}]);
I have a service that makes the API calls using angularjs $resource
angular.module('apitestApp')
.service('httpService', ['$rootScope', '$resource', '$localStorage', function ($rootScope, $resource, $localStorage) {
this.getRequest = function (url, params){
var res = $resource(url, params, {
query: {
method: 'GET',
isArray: true,
headers: { 'Authorization': 'Bearer ' + $localStorage.token.access_token }
}
});
return res.query().$promise;
};
this.refreshToken = function (){
var url = $rootScope.domainPath + this.authPath;
var request = $resource(url);
return request.get({
client_id: this.clientId,
client_secret: this.secret,
grant_type: 'refresh_token',
refresh_token: $localStorage.token.refresh_token
},
function (data){
$localStorage.token = data;
}
).$promise;
};
}]);
And finally an interceptor that handles all unauthorized requests (401), refresh the access token and retries the failed request.
angular.module('apitestApp')
.factory('apiInterceptor', ['$q', '$injector', function ($q, $injector){
//Handling error codes
return {
response : function (response){
return response || $q.when(response);
},
responseError: function (rejection){
switch(rejection.status){
case 400:
console.log("Bad request");
break;
case 401:
var config = rejection.config;
var deferred = $q.defer();
var httpService = $injector.get('httpService');
httpService.refreshToken().then(deferred.resolve, deferred.reject);
return deferred.promise.then(function (){
return httpService.getRequest(config.url, config.params);
});
//break;
case 500:
console.log("Internal server error");
break;
default:
console.log("Another error");
break;
}
return $q.reject(rejection);
}
};
}]);
When the access token is valid, getRequest() method in my service successfully returns a promise, this is the same I want the interceptor to return but is not. In case the access token has expired the interceptor catches a 401 error, then updates the access token and finally makes the same request, the problem is that my controller doesn't get any response of it.
How can I perform a refresh token action and return the expected data on the behalf of the user? What am I doing wrong in the interceptor?
You're going to want to remove the $rootScope provider from the controller, that is not best practices for Angular as the controller has it's own scope inside of $rootScope. Services and Factories are okay to put on the $rootScope as it does not create it's own scope and that is where they will listen for their own events.
Also, it's best practice to put any asynchronous activity/HTTP calls into the services/factories. Just remember "skinny controllers, fat services".
Maybe try using an async handler that uses a sort of publish/subscribe design. Now, if it fails, it will call to update the stored value of messages once the getRequest function has completed async, triggering an update to the scope digest for any controller subscribed to the method:
Controller
angular.module('apitestApp')
.controller('MainCtrl', ['$scope', 'httpService', function ($scope, httpService) {
$scope.messages = [];
httpService.setPath();
httpService.onMessageReady($scope, function (messagesData) {
$scope.messages = messagesData;
});
}]);
Service
angular.module('apitestApp')
.service('httpService', ['$rootScope', '$resource', '$localStorage', function ($rootScope, $resource, $localStorage) {
var self = this;
this.messages = undefined;
this.setPath = function () {
self.getRequest($rootScope.domainPath + $rootScope.apiPath + 'messages.json', {});
};
this.getRequest = function (url, params) {
var res = $resource(url, params, {
query: {
method: 'GET',
isArray: true,
headers: { 'Authorization': 'Bearer ' + $localStorage.token.access_token }
}
});
return res.query().$promise.then(function (data) {
if (data) {
self.messages = data;
$rootScope.$broadcast('messagesReady');
}
});
};
this.refreshToken = function (){
var url = $rootScope.domainPath + this.authPath;
var request = $resource(url);
return request.get({
client_id: this.clientId,
client_secret: this.secret,
grant_type: 'refresh_token',
refresh_token: $localStorage.token.refresh_token
},
function (data){
$localStorage.token = data;
}
).$promise;
};
this.onMessageReady = function (scope, callback) {
callback(this.messages);
scope.$on('messagesReady', function () {
callback(this.messages);
});
};
}]);
Related
I'm having trouble with callbacks and asynchronous requests in angular. I created a service to fetch the data which uses callbacks (once the calls_left hits 0, callback is called). I inject my service inside my controller and then call the getMInterval on my service (metr.getMInterval) which should initialize the data (pTypes in this case) and wait till it is finished. The problem is that pTypes logs first as empty Array []. Any ideas?
Service:
angular.module('mService', ['config']).service('metr', function($http, $window, $rootScope, $location, config) {
var pTypes = [];
var intervalData = {};
function fetchMInterval(callback) {
var calls_left = 1;
var onComplete = function() {
calls_left--;
if (calls_left == 0) {
console.log("finished retrieving data from api");
if (callback) callback();
}
};
var url = 'fetch_data_from_this_url';
$http({
method: 'GET',
url: url
}).then(function success(response) {
console.log(response);
var data = JSON.parse(response.data.split(config.DELIMITER)[1]);
intervalData = data["Data"];
pTypes = metrIntervalData["pTypes"];
onComplete();
}, function error(response) {
alert("Failed to fetch info from " + url + "! " + response.status);
onComplete();
});
}
return {
getMInterval:function() {return fetchMInterval();},
getPTypes:function() {return pTypes;},
}
Controller:
app.controller('mController', function($scope, $filter, metr, config) {
metr.getMInterval();
$scope.pTypes = metr.pTypes();
console.log(metr.pTypes()); //console logs Array []
});
I need to send authorization header in the below code which I am using to call restful service. All works fine but header info is not getting sent.
services.factory('downloadService', ['$q', '$timeout', '$window',
function ($q, $timeout, $window) {
return {
download: function (fileName) {
var defer = $q.defer();
$timeout(function () {
$window.location.href = 'lolo/download?fileName=' + fileName;
}, 1000)
.then(function () {
defer.resolve('success');
}, function () {
defer.reject('error');
});
return defer.promise;
}
};
}
]);
I need to send below Authorization header -
$http.defaults.headers.common.Authorization = $localStorage.authToken;
you can't pass header while using $window.location.href but you can always use cookies for authorization.
Though I would suggest to have a look on security problems with cookies before changing anything on your server implementation.
As #S4beR said, you can't pass header using $window.location.href.
A work around is to pass the token as query string or using cookie. However, those options suffer security problems.
Another approach is to request a temporary download token with a short lifetime, say 60 seconds. Then pass this download token as a query string in url.
services.factory('downloadService', ['$q', '$timeout', '$window', '$http', '$localStorage'
function ($q, $timeout, $window, $http, $localStorage) {
return {
download: function (fileName) {
var defer = $q.defer();
var req = {
method: 'POST',
url: '/download/token',
headers: {
'Authorization': $localStorage.get('token')
}
}
$http(req)
.then( function (token) {
$timeout(function () {
$window.open('lolo/download?fileName=' + fileName +"&token="+token, "_blank") = ;
}, 1000)
})
.then(function () {
defer.resolve('success');
}, function () {
defer.reject('error');
});
return defer.promise;
}
};
}
]);
I`m trying to make a request to an API server with $resource.
I want to make a post but angular turns post method into options and give an error like
OPTIONS http: / /l ocalhost/API.DWS/api/v1/user/login
XMLHttpRequest cannot load http:/ / localhost/API.DWS/api/v1/user/login. Response for preflight has invalid HTTP status code 405
var objectMethods = {
get: { method: 'GET' },
update: { method: 'PUT' },
create: { method: 'POST' },
remove: { method: 'DELETE' },
patch: { method: 'PATCH' }
};
var apiUrl = "http://localhost/API.DWS";
angular.module('nurby.version.services', [])
.config(function ($httpProvider) {
})
.factory('LoginService', ['$resource', '$http', function ($resource, $http) {
return $resource(apiUrl + "/api/v1/user/login", {},objectMethods);
}])
.controller('LogInController', ['$scope', '$rootScope', '$location','LoginService', '$http', function ($scope, $rootScope, $location, LoginService, $http) {
$scope.login = function (model) {
var loginObject = { Username: model.username, Password: model.password };
$http.defaults.useXDomain = true;
$http.defaults.headers['Content-Type'] = 'application/json';
$http.defaults.headers['Access-Control-Allow-Origin'] = '*';
LoginService.create({}, loginObject, function (data) {
if (data) {
toastr.success("itworks");
}
else {
toastr.error("not working")
}
})
}
}]);
you can define service.js and use it like below:
var APP_NAME = 'app';
angular.module(APP_NAME).service('WebService', ["$http", function ($http) {
this.login = function (parameters,callbackFunc)
{
$http({
url: 'api/login',
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: $.param(parameters)
}).success(function (data) {
callbackFunc(data);
}).error(function (data) {
callbackFunc([]);
});
};
and use it in your controller like below:
LoginController = ['$scope', '$http', '$location', 'WebService','$window', function ($scope, $http, $location,$WebService,$window) {
$scope.login = function(admin){
var data = {email:admin.email,password:admin.password};
$WebService.login(data,function(result){
if(result.success){
$window.location.replace("index");
}
else{
$scope.loginError = result.fail;
}
});
}
}];
The problem here is that you are specifying a complete URL beginning "http://localhost/API.DWS" and you haven't loaded the web page from the same domain (maybe you used a different port?).
This means the browser sees your request as a Cross-Domain request. It therefore sends an OPTIONS request first to ask the server whether it will permit you to send the POST. You could configure your server to respond correctly to these requests, or change your code so the web page and the api are on the same domain.
How to configure your server will depend on which server you are running. Search for CORS and your web server and you should find useful information.
Inside my controller this worked for me
var resource = $resource(
"your_api_url",
{
callback: "JSON_CALLBACK"
},
{
getData: {
method: "JSONP",
isArray: false
}
}
);
function loadRemoteData() {
$scope.isLoading = true;
resource.getData().$promise.then(
function( friends ) {
$scope.isLoading = false;
},
function( error ) {
// If something goes wrong with a JSONP request in AngularJS,
// the status code is always reported as a "0". As such, it's
// a bit of black-box, programmatically speaking.
alert( "Something went wrong!" );
}
);
}
$scope.searchResources = function() {
$scope.isLoading = true;
resource.getData().$promise.then(
function( friends ) {
$scope.isLoading = false;
},
function( error ) {
// If something goes wrong with a JSONP request in AngularJS,
// the status code is always reported as a "0". As such, it's
// a bit of black-box, programmatically speaking.
alert( "Something went wrong!" );
}
);
};
Using AngularJS for my application and for http post the server needs a token that we can get by http get. But I want to run the token_generate() function before each http call, because sometimes the token expires
token = function() {
var api = AuthService.getToken();
api.success(function (response) {
var token = response.token;
$http.defaults.headers.common['X-CSRF-TOKEN'] = token;
});
};
token();
You need to register an $http interceptor:
(function(window, angular) {
function tokenizerConfig($httpProvider) {
function registerInterceptors($q, $http, AuthenticationService) {
var interceptors = {};
interceptors.request = function(configs) {
if(AuthenticationService.isTokenValid) {
return $q.when(configs);
}
var qs = {};
return $http
.get(TOKEN_API, { cache: false, params: qs})
.then(function(result) {
AuthenticationService.setToken(result.data.token);
return configs;
})
;
};
return interceptors;
}
$httpProvider.interceptors.push(['$q', '$http', 'AuthenticationService', registerInterceptors]);
}
angular
.module('tokenizer', [])
.config(['$httpProvider', tokenizerConfig])
})(window, window.angular);
var app = angular.module('app', ['ngResource']);
app.factory('UserFactory', function ($resource) {
return $resource('/com/vsoft/rest/users', {}, {
query: {
method: 'GET',
params: {},
isArray: false
}
});
});
app.controller('MyCtrl1', ['$scope', 'UserFactory', function ($scope, UserFactory) {
UserFactory.get({}, function (userFactory) {
$scope.firstname = userFactory.firstName;
$scope.lastname = userFactory.lastName;
});
});
}]);
i added above app in my html.But the app and angular-resource.js but my app.js is not exeuting.
If i removed ngResource module and $resource alert is coming.But if i used ngResource im nt getting alert.
Please help in this.If any one knows any Good Example to use Restful services with angularjs .Please Kindly send Url or code.
Please help me.
i called{{firstname}}
in my html but its not coming .
I use a service for handling RESTful messages
app.service('restService', function ($http, $log) {
'use strict';
var self = this;
var BASE_URL = "base/url/";
//First way how to do it
self.httpGet = function (url) {
$log.info("HTTP Get", url);
return postProcess($http({method: 'GET', url: BASE_URL + url}));
};
//Second way how to do it
self.httpPut = function (url, object) {
$log.info("HTTP Put", url);
return postProcess($http.put(BASE_URL + url, object));
};
self.httpPost = function (url, object) {
$log.info("HTTP Post", url);
return postProcess($http.post(BASE_URL + url, object));
};
self.httpDelete = function (url) {
$log.info("HTTP Delete", url);
return postProcess($http.delete(BASE_URL + url));
};
function postProcess(httpPromise) {
return httpPromise.then(function (response) {
if (response.status === 200) {
return response;
}
//Other than 200 is not ok (this is application specific)
failure(response);
}, function (response) {
failure(response);
});
}
/**
* Promise for failure HTTP codes
* #param response the HTTP response
*/
function failure(response) {
//Error handling
}
});
usable as
restService.httpGet("categories").then(function (response) {
categoryData = angular.fromJson(response.data);
//Broadcast an event to tell that the data is ready to be used
$rootScope.$broadcast("categoriesReady");
});