AngularJS: How to send auth token with $resource requests? - javascript

I want to send an auth token when requesting a resource from my API.
I did implement a service using $resource:
factory('Todo', ['$resource', function($resource) {
return $resource('http://localhost:port/todos.json', {port:":3001"} , {
query: {method: 'GET', isArray: true}
});
}])
And I have a service that stores the auth token:
factory('TokenHandler', function() {
var tokenHandler = {};
var token = "none";
tokenHandler.set = function( newToken ) {
token = newToken;
};
tokenHandler.get = function() {
return token;
};
return tokenHandler;
});
I would like to send the token from tokenHandler.get with every request send via the Todo service. I was able to send it by putting it into the call of a specific action. For example this works:
Todo.query( {access_token : tokenHandler.get()} );
But I would prefer to define the access_token as a parameter in the Todo service, as it has to be sent with every call. And to improve DRY.
But everything in the factory is executed only once, so the access_token would have to be available before defining the factory and it cant change afterwards.
Is there a way to put a dynamically updated request parameter in the service?

Thanks to Andy Joslin. I picked his idea of wrapping the resource actions. The service for the resource looks like this now:
.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
var resource = $resource('http://localhost:port/todos/:id', {
port:":3001",
id:'#id'
}, {
update: {method: 'PUT'}
});
resource = tokenHandler.wrapActions( resource, ["query", "update"] );
return resource;
}])
As you can see the resource is defined the usual way in the first place. In my example this includes a custom action called update. Afterwards the resource is overwritten by the return of the tokenHandler.wrapAction() method which takes the resource and an array of actions as parameters.
As you would expect the latter method actually wraps the actions to include the auth token in every request and returns a modified resource. So let's have a look at the code for that:
.factory('TokenHandler', function() {
var tokenHandler = {};
var token = "none";
tokenHandler.set = function( newToken ) {
token = newToken;
};
tokenHandler.get = function() {
return token;
};
// wrap given actions of a resource to send auth token with every
// request
tokenHandler.wrapActions = function( resource, actions ) {
// copy original resource
var wrappedResource = resource;
for (var i=0; i < actions.length; i++) {
tokenWrapper( wrappedResource, actions[i] );
};
// return modified copy of resource
return wrappedResource;
};
// wraps resource action to send request with auth token
var tokenWrapper = function( resource, action ) {
// copy original action
resource['_' + action] = resource[action];
// create new action wrapping the original and sending token
resource[action] = function( data, success, error){
return resource['_' + action](
angular.extend({}, data || {}, {access_token: tokenHandler.get()}),
success,
error
);
};
};
return tokenHandler;
});
As you can see the wrapActions() method creates a copy of the resource from it's parameters and loops through the actions array to call another function tokenWrapper() for every action. In the end it returns the modified copy of the resource.
The tokenWrappermethod first of all creates a copy of preexisting resource action. This copy has a trailing underscore. So query()becomes _query(). Afterwards a new method overwrites the original query() method. This new method wraps _query(), as suggested by Andy Joslin, to provide the auth token with every request send through that action.
The good thing with this approach is, that we still can use the predefined actions which come with every angularjs resource (get, query, save, etc.), without having to redefine them. And in the rest of the code (within controllers for example) we can use the default action name.

Another way is to use an HTTP interceptor which replaces a "magic" Authorization header with the current OAuth token. The code below is OAuth specific, but remedying that is a simple exercise for the reader.
// Injects an HTTP interceptor that replaces a "Bearer" authorization header
// with the current Bearer token.
module.factory('oauthHttpInterceptor', function (OAuth) {
return {
request: function (config) {
// This is just example logic, you could check the URL (for example)
if (config.headers.Authorization === 'Bearer') {
config.headers.Authorization = 'Bearer ' + btoa(OAuth.accessToken);
}
return config;
}
};
});
module.config(function ($httpProvider) {
$httpProvider.interceptors.push('oauthHttpInterceptor');
});

I really like this approach:
http://blog.brunoscopelliti.com/authentication-to-a-restful-web-service-in-an-angularjs-web-app
where the token is always automagically sent within the request header without the need of a wrapper.
// Define a new http header
$http.defaults.headers.common['auth-token'] = 'C3PO R2D2';

You could create a wrapper function for it.
app.factory('Todo', function($resource, TokenHandler) {
var res= $resource('http://localhost:port/todos.json', {
port: ':3001',
}, {
_query: {method: 'GET', isArray: true}
});
res.query = function(data, success, error) {
//We put a {} on the first parameter of extend so it won't edit data
return res._query(
angular.extend({}, data || {}, {access_token: TokenHandler.get()}),
success,
error
);
};
return res;
})

I had to deal with this problem as well. I don't think if it is an elegant solution but it works and there are 2 lines of code :
I suppose you get your token from your server after an authentication in SessionService for instance. Then, call this kind of method :
angular.module('xxx.sessionService', ['ngResource']).
factory('SessionService', function( $http, $rootScope) {
//...
function setHttpProviderCommonHeaderToken(token){
$http.defaults.headers.common['X-AUTH-TOKEN'] = token;
}
});
After that all your requests from $resource and $http will have token in their header.

Another solution would be to use resource.bind(additionalParamDefaults), that return a new instance of the resource bound with additional parameters
var myResource = $resource(url, {id: '#_id'});
var myResourceProtectedByToken = myResource.bind({ access_token : function(){
return tokenHandler.get();
}});
return myResourceProtectedByToken;
The access_token function will be called every time any of the action on the resource is called.

I might be misunderstanding all of your question (feel free to correct me :) ) but to specifically address adding the access_token for every request, have you tried injecting the TokenHandler module into the Todo module?
// app
var app = angular.module('app', ['ngResource']);
// token handler
app.factory('TokenHandler', function() { /* ... */ });
// inject the TokenHandler
app.factory('Todo', function($resource, TokenHandler) {
// get the token
var token = TokenHandler.get();
// and add it as a default param
return $resource('http://localhost:port/todos.json', {
port: ':3001',
access_token : token
});
})
You can call Todo.query() and it will append ?token=none to your URL. Or if you prefer to add a token placeholder you can of course do that too:
http://localhost:port/todos.json/:token
Hope this helps :)

Following your accepted answer, I would propose to extend the resource in order to set the token with the Todo object:
.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
var resource = $resource('http://localhost:port/todos/:id', {
port:":3001",
id:'#id'
}, {
update: {method: 'PUT'}
});
resource = tokenHandler.wrapActions( resource, ["query", "update"] );
resource.prototype.setToken = function setTodoToken(newToken) {
tokenHandler.set(newToken);
};
return resource;
}]);
In that way there is no need to import the TokenHandler each time you want to use the Todo object and you can use:
todo.setToken(theNewToken);
Another change I would do is to allow default actions if they are empty in wrapActions:
if (!actions || actions.length === 0) {
actions = [];
for (i in resource) {
if (i !== 'bind') {
actions.push(i);
}
}
}

Related

Angularjs : How to always show modal login dialog if any REST API fails due to authentication

My angular JS application is for an e-commerce usecase. There would be several pages, where some data would be fetched from some REST APIs which would be authenticated (and some not requiring authentication). If authentication fails (user not logged in), the APIs would all respond with a special error_code (say 'AUTH_FAIL').
My requirement is if any API fails due to authentication, then a login modal form dialog should appear in that page. This modal form contains the Username and password field. If the login succeeds, the modal window should close, and the current route should be re-freshed.
I understand how to do this for a particular route/controller. However, since there would be a lot of such pages where this would be needed, I'm unable to think of a way in which same piece of code could be easily utilized, since in my opinion, this does seem like a common requirement. How can it be done, or if not, what's the best way around it?
You can use interceptors for this purpose. Inteceptors can be used for global error handling, authentication, or any kind of synchronous or asynchronous pre-processing of request or postprocessing of responses.
For example I use the following code to redirect user to login when authentication fails.
.factory('myInterceptor', ['$q', '$location', '$injector', function ($q, $location, $injector) {
return {
response: function (response) {
return response || $q.when(response);
},
responseError: function (rejection) {
if (rejection.status === 401) {
var stateService = $injector.get('$state');
stateService.go('login');
}
return $q.reject(rejection);
}
}
}])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
}]);
Using interceptors sounds like the most obvious and elegant solution, however I was never satisfied with it, mostly because of running into the circular dependency problems.
Here are some bits and pieces of logic from one of my apps using angular 1.6 and ui-router.
Some explanation about the business logic before you deep dive into the code.
I use JWT authentication and my server expects JWT to be passed as a header, hence the specifics of the authService implementation. The authService checks if the header is expired, and tries to send a JWT refresh request before actually showing a login dialog. Feel free to adjust it to your security implementation (e.g. session cookie/based or some other storage).
authService.js
This service is responsible for storing security token in the client. It returns a promise, which is resolved with the JWT token (if present or if it was refreshed). The promise is rejected when the token is expired and the service failed to obtain new token from the server.
app.factory('authService', function($http, $q, $window, jwtHelper, API_HOST) {
var storage = $window.localStorage;
var cacheToken = {};
var targetUrl = null;
function saveToken(data) {
var tokenPayload = jwtHelper.decodeToken(data.auth_token);
storage.setItem('auth_token', data.auth_token);
storage.setItem('refresh_token', data.refresh_token);
storage.setItem('exp', tokenPayload.exp);
storage.setItem('user_identifier', tokenPayload.user_identifier);
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
cacheToken.user_identifier = storage.getItem('user_identifier');
}
function setCacheToken() {
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
cacheToken.user_identifier = storage.getItem('user_identifier');
}
function isAuthenticated() {
return cacheToken.auth_token && cacheToken.exp > moment(new Date().getTime()).unix()
}
setCacheToken();
return {
saveToken: function(data) {
saveToken(data);
return cacheToken;
},
getToken: function() {
return cacheToken;
},
isAuthenticated: isAuthenticated,
targetUrl: targetUrl,
getAuthorizationHeader: function() {
if (isAuthenticated()) {
return $q.when({
'Authorization': 'Bearer ' + cacheToken.auth_token
});
} else {
cacheToken.auth_token = storage.getItem('auth_token');
cacheToken.refresh_token = storage.getItem('refresh_token');
cacheToken.exp = storage.getItem('exp');
if (isAuthenticated()) {
return $q.when({
'Authorization': 'Bearer ' + cacheToken.auth_token
});
} else {
if (!cacheToken.refresh_token) return $q.reject(null);
return $http.post(API_HOST + '/tokens/refresh', {
'refresh_token': cacheToken.refresh_token
}).then(function(response) {
saveToken(response.data);
return {
'Authorization': 'Bearer ' + cacheToken.auth_token
};
}).catch(function() {
cacheToken = {};
$window.localStorage.clear();
return $q.reject(null);
})
}
}
}
}
});
app.run block
This piece of logic is responsible for memorising the target url in case user tried to access protected resource, or when user token/session is expired. Please 2 things here: authService.targetUrl stores the URL and authenticate property on the ui-router state is used to check if the state is protected (e.g. if the authentication logic should be applied).
$transitions.onBefore({
to: function(state) {
return state.self.authenticate;
}
}, function(trans) {
return authService.getAuthorizationHeader().then(function() {
return null;
}).catch(function() {
authService.targetUrl = $window.location.href;
$('#login-modal').modal();
return trans.router.stateService.target('homepage');
});
});
login modal directive
This piece of code stores the user token after login and also checks if the targetUrl is present in the authService, e.g. if a user tried to access protected resource some time before.
scope.loginCallback = function(response) {
authService.saveToken(response.data);
jasprApi.User.me().then(function(response) {
$rootScope.user = response.data;
$(element).modal('hide');
if (authService.targetUrl) {
$window.location.href = authService.targetUrl;
authService.targetUrl = null;
}
});
};
routes.js
Here is the ui-router states config which specified if the state should be protected
.state('admin', {
url: '/admin',
//other configuration
//...
//...
authenticate: true
})
api.js
A bonus — this is the sample from the file with the methods for accessing the API. Please note how authService is used here.
updatePageAction: function() {
return authService.getAuthorizationHeader().then(function(authHeader) {
return $http({
method: 'PUT',
url: '/admin/page/update',
headers: authHeader
});
});
},
I hope it helps!
Cheers

How to pass Request Body from AngularJS with API

I am trying to use json using API to retrieve data on a Google Map in AngularJS.
This is my code in Angular:
$scope.loadData = function () {
var map_info = {
'ApiKey': '1iVuQy3FGK39d51',
'ProjectId': '11'
};
var url = "http://localhost:63411/api/clientportal/?action=mapjson";
return $http.post(url, map_info).then(function (response) {
return response.data.MapData;
});
};
But, when I run the code, it shown 'Cross-Origin Reuest Block' error.
Then, I search from the internet about this error, I come out with a solution to change $http.post to $http.jsonp.
$scope.loadData = function () {
var map_info = {
'ApiKey': '1iVuQy3FGK39d51',
'ProjectId': '11'
};
var url = "http://localhost:63411/api/clientportal/?action=mapjson";
return $http.jsonp(url, map_info).then(function (response) {
return response.data.MapData;
});
};
It does go to the URL, however the Request Body is empty.
Since I created the API using C#, the 'Request.InputStream' is null.
string jsonPosted = new StreamReader(Request.InputStream).ReadToEnd();
So the 'jsonPosted' is null.
I did try on Postman, and it works. But not in Angular.
How do I send the Request Body properly?
I want the 'ApiKey' and 'ProjectId' to be included on Request Body.
Thank You.

Ember Understand execution flow between route/controller

I have a "box" route/controller as below;
export default Ember.Controller.extend({
initialized: false,
type: 'P',
status: 'done',
layouts: null,
toggleFltr: null,
gridVals: Ember.computed.alias('model.gridParas'),
gridParas: Ember.computed('myServerPars', function() {
this.set('gridVals.serverParas', this.get('myServerPars'));
this.filterCols();
if (!this.get('initialized')) {
this.toggleProperty('initialized');
} else {
Ember.run.scheduleOnce('afterRender', this, this.refreshBox);
}
return this.get('gridVals');
}),
filterCols: function()
{
this.set('gridVals.layout', this.get('layouts')[this.get('type')]);
},
myServerPars: function() {
// Code to set serverParas
return serverParas;
}.property('type', 'status', 'toggleFltr'),
refreshBox: function(){
// Code to trigger refresh grid
}
});
My route looks like;
export default Ember.Route.extend({
selectedRows: '',
selectedCount: 0,
rawResponse: {},
model: function() {
var compObj = {};
compObj.gridParas = this.get('gridParas');
return compObj;
},
activate: function() {
var self = this;
self.layouts = {};
var someData = {attr1:"I"};
var promise = this.doPost(someData, '/myService1', false); // Sync request (Is there some way I can make this work using "async")
promise.then(function(response) {
// Code to use response & set self.layouts
self.controllerFor(self.routeName).set('layouts', self.layouts);
});
},
gridParas: function() {
var self = this;
var returnObj = {};
returnObj.url = '/myService2';
returnObj.beforeLoadComplete = function(records) {
// Code to use response & set records
return records;
};
return returnObj;
}.property(),
actions: {
}
});
My template looks like
{{my-grid params=this.gridParas elementId='myGrid'}}
My doPost method looks like below;
doPost: function(postData, requestUrl, isAsync){
requestUrl = this.getURL(requestUrl);
isAsync = (isAsync == undefined) ? true : isAsync;
var promise = new Ember.RSVP.Promise(function(resolve, reject) {
return $.ajax({
// settings
}).success(resolve).error(reject);
});
return promise;
}
Given the above setup, I wanted to understand the flow/sequence of execution (i.e. for the different hooks).
I was trying to debug and it kept hopping from one class to another.
Also, 2 specific questions;
I was expecting the "activate" hook to be fired initially, but found out that is not the case. It first executes the "gridParas" hook
i.e. before the "activate" hook. Is it because of "gridParas"
specified in the template ?
When I do this.doPost() for /myService1, it has to be a "sync" request, else the flow of execution changes and I get an error.
Actually I want the code inside filterCols() controller i.e.
this.set('gridVals.layout', this.get('layouts')[this.get('type')]) to
be executed only after the response has been received from
/myService1. However, as of now, I have to use a "sync" request to do
that, otherwise with "async", the execution moves to filterCols() and
since I do not have the response yet, it throws an error.
Just to add, I am using Ember v 2.0
activate() on the route is triggered after the beforeModel, model and afterModel hooks... because those 3 hooks are considered the "validation phase" (which determines if the route will resolve at all). To be clear, this route hook has nothing to do with using gridParas in your template... it has everything to do with callling get('gridParas') within your model hook.
It is not clear to me where doPost() is connected to the rest of your code... however because it is returning a promise object you can tack on a then() which will allow you to essentially wait for the promise response and then use it in the rest of your code.
Simple Example:
this.doPost().then((theResponse) => {
this.doSomethingWith(theResponse);
});
If you can simplify your question to be more clear and concise, i may be able to provide more info
Generally at this level you should explain what you want to archive, and not just ask how it works, because I think you fight a lot against the framework!
But I take this out of your comment.
First, you don't need your doPost method! jQuerys $.ajax returns a thenable, that can be resolved to a Promise with Ember.RSVP.resolve!
Next: If you want to fetch data before actually rendering anything you should do this in the model hook!
I'm not sure if you want to fetch /service1, and then with the response you build a request to /service2, or if you can fetch both services independently and then show your data (your grid?) with the data of both services. So here are both ways:
If you can fetch both services independently do this in your routes model hook:
return Ember.RSVP.hash({
service1: Ember.RSVP.resolve($.ajax(/*your request to /service1 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
service2: Ember.RSVP.resolve($.ajax(/*your request to /service2 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
});
If you need the response of /service1 to fetch /service2 just do this in your model hook:
return Ember.RSVP.resolve($.ajax(/*/service1*/)).then(service1 => {
return Ember.RSVP.resolve($.ajax(/*/service2*/)).then(service2 => {
return {
service1,
service2
}; // this object will then be available as `model` on your controller
});
});
If this does not help you (and I really think this should fix your problems) please describe your Problem.

Proper use of transformers vs interceptors

When POSTing to an endpoint in a service layer to update a user's profile, I need to strip certain values from the request payload (the profile with the desired modifications from the client) and re-attach them in the response payload (the updated profile from the server). I am currently performing behavior using Angular's request and response transformers, like this:
myService.updateProfile = function (profile) {
return $http({
method: 'POST',
withCredentials: true,
url: root + 'users/profile',
data: profile,
transformRequest : requestTransformer,
transformResponse : responseTransformer
});
};
// the map used during transformation below
var myMap = {
0: 'foo',
1: 'bar',
2: 'etc'
};
// prependTransform() and appendTransform() are similar to the example provided in Angular transformer docs here:
// https://docs.angularjs.org/api/ng/service/$http#overriding-the-default-transformations-per-request
var requestTransformer = httpTransformer.prependTransform($http.defaults.transformRequest, function(profileRequest) {
profileRequest.myKey = myMap.indexOf(profileRequest.myValue);
delete profileRequest.myValue;
return profileRequest;
});
var responseTransformer = httpTransformer.appendTransform($http.defaults.transformResponse, function(profileResponse) {
profileRequest.myValue = myMap[profileRequest.myKey];
delete profileRequest.myKey;
return profileResponse;
});
I prepend a transformer to the default request transformers and append a transformer to the default response transformers. My question is, is there a better way to do this? Perhaps using interceptors, as documented here, instead? If so, how?
I think your solution is fine but if you want an alternative, you can intercept specific requests like so. HTTP interceptors are mostly useful for handling global HTTP requests/responses (auth, error handling, etc.).
In any case, the "response" payload should be taken cared of from the API/server-side.
$provide.factory('userProfileInterceptor', function() {
return {
request: function(config) {
if (config.url.indexOf('/users/profile') >=0){
if (config.params.myValue) delete config.params.myValue;
}
return config;
},
response: function(response) {
if (response.config.url.indexOf('/users/profile') >=0){
delete response.data.myKey;
}
return response;
}
};
});
$httpProvider.interceptors.push('userProfileInterceptor');

AngularJS $resource 'GET' accesses the correct API, but 'PUT' and 'POST' do not

Follow up from AngularJS $resource calls the wrong API URL when using method:POST
My controller is set up like this, with Angular's $resource:
$scope.updateProduct = $resource('/api/updateProduct/:product/:param/:value',{},{
query: {method:'GET'},
post: {method:'POST'},
save: {method:'PUT', params: {brand: '#brand', param:'#param', value:'#value'}},
remove: {method:'DELETE'}
});
$scope.updateProduct.save({
product : $scope.post._id,
param: 'likes',
value: $scope.user._id
});
My server runs on NodeJS and ExpressJS. In my console, when the save operation is called, I can see:
POST /api/updateBrand/<productid>/likes/fun,%20quirky%20loud,%20boho,%20hippy 200 22ms - 2.31kb
However, my API is not being correctly accessed. For instance, if I go to the above URL in my browser, the API function is called, and my database is updated (and it is reported in my server's console). Yet when Angular does a PUT on this URL, nothing happens at all.
Interestingly, when I change $scope.updateProduct.save() to $scope.updateProduct.get(), the API is correctly called and everything works fine.
Any ideas what's going on here?
EDIT: Here's the server setup:
ExpressJS API setup:
app.get('/api/updateProduct/:product/:param/:value', api.updateProduct);
API code
exports.updateProduct = function (req, res) {
console.log("TEST")
var product = req.params.product;
var param = req.params.param;
var value = req.params.value;
var props = { $push: {} };
if(param == 'userTags'){
var oldVal = value;
value = oldVal.match(/[-'"\w]+/g);
props.$push[param];
props.$push[param] = {$each: []};
props.$push[param].$each = value;
}else{
var props = { $push: {} };
props.$push[param] = value;
}
db.products.update({"_id": ObjectId(product)}, props, function (err, record) {
if (err || !(record)) {
console.log("Lookup Error: " + err);
} else{
console.log("Updated " + product + " with " + param);
console.log(record);
res.json({obj:record})
}
});
};
It seems that your server is not waiting for a POST or PUT request, but a GET request as per your configuration.
app.get('/api/updateProduct/:product/:param/:value', api.updateProduct);
According to the ExpressJS API (http://expressjs.com/api.html), you should be able to replace the get with any valid http verb.
app.VERB(path, [callback...], callback)
app.post('/api/updateProduct/:product/:param/:value', api.updateProduct);
app.put('/api/updateProduct/:product/:param/:value', api.updateProduct);

Categories

Resources