How to inject services to monitor authentication and storage status? - javascript

I'm using Angular.js to build a client side application with Restangular.
The problem is, I implemented a digest authentication with a wsse header which i have to generate at each request to my REST Server.
I saw that Restangular provide a function : addFullRequestInterceptor().
So now, i'm trying to use it inside my RestangularProvider to configure it in this file restangular.js :
// Config Restangular
app.config(function(RestangularProvider) {
RestangularProvider.setBaseUrl(applicationConfig.SERVER_URL);
RestangularProvider.setDefaultHeaders({'Content-Type': 'application/json'});
RestangularProvider.addFullRequestInterceptor(
function (element, operation, route, url, headers, params, httpConfig) {
// Here is my header generation.
$http.defaults.headers.common['X-WSSE'] =
TokenHandler.getCredentials(
AuthHandler.authentication.user.username,
AuthHandler.authentication.secret);
return {
element: element,
headers: headers,
params: params,
httpConfig: httpConfig
};
});
});
But i have an injection problem and i can't find out how to inject my TokenHandler service & my AuthHandler service to be able to know if the user is already loggedin or not and if he has localstroage or not.
Thanks for the help ;)

The problem is that in the .config phase the services, like AuthHandler and TokenHandler are not yet instantiated, so you need to instantiate them (and their dependencies) manually with $injector (which is injectable into config blocks):
app.config(function(RestangularProvider,
$injector,
AuthHandlerProvider,
TokenHandlerProvider) {
var AuthHandler = $injector.instantiate(AuthHandlerProvider.$get);
var TokenHandler = $injector.instantiate(TokenHandlerProvider.$get);
//...
});

Related

Angular $http Interceptors for Auth Tokens

I'm trying to write a $http interceptor which will attach a header in order to authorize the application with a token.
I have an auth service which requests and stores the token for future use.
In the application config, I'm configuring $httpProvider and pushing a new interceptor onto the array.
The interceptor depends on my Auth service, in order to get hold of the token to send each time. In turn, the Auth service depends on $http in order to send the initial request to authenticate and retrieve the auth token.
I end up with a circular dependency graph that looks something like:
$httpProvider
^ \
/ v
$http <- Auth Service
Auth depends on $http, and through $httpProvider, $http depends on Auth.
Is there an elegant way to get around this? I thought about using an intermediate service, but ultimately, that would just be extending the dependency graph. Something needs to be fundamentally changed.
Is it possible to do something like resolve and reconfigure $http after the auth token has been retrieved?
You want to use $injector to manually get a hold of your authentication service.
angular.module('app.services.authentication')
.factory('AuthenticationHeaderInterceptor', ['$injector', AuthenticationHeaderInterceptor]);
function AuthenticationHeaderInterceptor ($injector) {
var service = {
request: addAuthenticationHeader
};
return service;
function addAuthenticationHeader (config) {
var token = null;
// Need to manually retrieve dependencies with $injector.invoke
// because Authentication depends on $http, which doesn't exist during the
// configuration phase (when we are setting up interceptors).
// Using $injector.invoke ensures that we are provided with the
// dependencies after they have been created.
$injector.invoke(['Authentication', function (Authentication) {
token = Authentication.getAuthenticationHeaders();
}]);
if (token) {
angular.extend(config.headers, token);
}
return config;
}
}

Globals across Angular services?

Can I define global variables or functions that can be accessed by all of my Angular service modules?
For example, I have several services that make http requests to various endpoints of the same url root. I would like to be able to change this root.
My service modules look like this.
var host = 'http://my-api.com';
return {
get: function(id) {
return $http({
url: host + '/contacts/' + id,
method: 'GET'
});
},
...
switchHost: function(url){
host = url;
}
});
So I can call ContactService.switchHost('http://new-url') in my Controllers to update this particular Service.
I would like to have some sort of Root Service where I coul define host and switchHost globally.
Note: The use case here is that some of our clients will be accessing our company API, and others will be self-hosting their resources.
i suggest you to create an interceptor which will digest an angular value like this.
angular.module('...')
.value('HOST', 'http://www.fu.bar.com/')
.factory('InterceptorFactory', function (HOST) {
return {
request: function(config) {
config.url = HOST + config.url;
return config;
}
};
})
.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('InterceptorFactory');
}]);
Whenever you call the $http service, the factory will be called and will add your host.
If you want to update the host, you just need to inject in any block the value and update it.
Thanks for the responses. I just did something like this.
angular.module('app.services').factory('RootService', function($http) {
return {
switchHost: function(url){
this.host = url;
},
host: 'http://app.apiary-mock.com'
}
});
This way the client can just type in a url on the settings page and the root will change, which is what I want.

Using Angular.js - how to serve binary data from a backend that requires authentication?

In my angularjs application I am communicating with a backend server that requires basic access authentication via http header. I have implemented the authentication mechanism on the client side as described here.
angular.module('myAuthModule')
.config(['$httpProvider', '$stateProvider',
function ($httpProvider, $stateProvider) {
$httpProvider.interceptors.push('securityInterceptor');
}])
.factory('securityInterceptor', ['$location', '$window', '$q',
function ($location, $window, $q) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers['Auth-Key'] = $window.sessionStorage.token;
}
return config;
},
response: function (response) {
if (response.status === 401 || response.status === 403) {
$location.path('/login');
}
return response || $q.when(response);
}
};
}
]);
So far so good, handling xhr requests within the angular app works as expected.
The problem is that I need to provide a download link for pdf documents. My backend server has a /Document/Pdf/:id resource that serves a application/pdf response with ContentDisposition: attachment which also requires authentication. I understand that I cannot initiate a download using xhr, however both providing a link to the document download via ngHref and calling a function that does for example $window.open('/Document/Pdf/13') lead to a 401 Unauthorized response by the server.
What am I missing here?
Having explored the possibilities given by #Geoff Genz with the addition of a fourth - data-uri option, which unfortunately does not allow defining filenames - I decided to go for a different approach.
I added a method to the API which generates a one-time download link based on a normally authenticated request and download it straight away. The angular handler becomes very simple
.factory('fileFactory', ['$http', '$window',
function ($http, $window) {
return {
downloadFile: function (fileId) {
return $http(
{
method: "POST",
data: fileId,
url: '/api/Files/RequestDownloadLink',
cache: false
}).success(function (response) {
var url = '/api/File/' + response.downloadId;
$window.location = url;
});
}
};
}]);
This is not perfect but I feel is least hack-ish. Also this works for me because I have full control of the front- and back-end.
There is not a simple solution to this. You've already discovered that you cannot download via Ajax, so you can't set a custom header that way. Nor can you set a custom header on a browser generated GET (like an href) or POST (like a form submit). I can suggest three different approaches, all of which will require some modifications on your server:
(1) Use Basic or Digest auth on your web page, so the browser will generate and send the Authorization header with those credentials.
(2) Set the token in "authorization" cookie that will be passed with the request and validate the token server side.
(3) Finally, the way we've implemented this is to use a POST request instead of a GET for the download. We POST to a hidden IFrame on the same page and have the server set the appropriate Content-Disposition header such as "attachment; filename="blah.pdf"" on the response. We then send the authorization token as a hidden field in the form.
None of these are ideal, and I know our solution feels kind of hacky, but I've not seen any more elegant approaches.

Angularjs $resource url intercept url encoding

I'm working on a sort of file-manager application that connects to a RESTFUL file api.
On the angular app, each file and directory is an instance of angular $resource using the file-object property relativePathName as resource id .
js
var File = $resource(url + '/:type/:id', {id: '#relativePathName', type: '#type'}, {…});
The problem is, when updating a file resource, the relativePathName parameter gets url encoded, e.g. / becomes %2F which causes the server to intercept the request before it hits the actual API (I assume the server treats this as a physical address and of returns a 404 response). The API is capable of treating whole url segments as a single param, so basically it'd treat path/to/file as a uri parameter of http://myapp.com/api/files/create/path/to/file and not as a different uri.
My question is, is there a way to modify the request url after it's being generated by the private Router instance inside of the resource constructor? If so, how (found nothing on this in the docs)?. What would be a possible solution? passing relativePathName as a parameter instead of declaring it as the resource id (which would require modifying the API)?
Thanks in advance.
Thomas
Using $resource is not the one stop shop for RESTful service calls, it is merely a convenience service for api's that are structured in a certain way. If $resource cannot do what you need, just create your own service using a mix of $resource and $http that that fits the api you are trying to call.
In our app we wanted a different URL for getByName requests, so we override the resource address with the URL parameter of the action getByName like so:
myapp.factory('ListGroup', ['$resource',
function($resource) {
return $resource(
'/API/List/:Id',
{
Id:'#Id',
Name:'#Name'
},
{
getByName: {method: 'GET', url: '/API/List/Group/:Name', isArray: true}
}
);
}
]);
My question is, is there a way to modify the request url after it's being generated by the private Router instance inside of the resource constructor?
I'm not sure about inside of the resource constructor but you can use interceptors to programatically alter route urls after they have been generated by $resource.
structure
hooks.config.js
hooks.factory.js
hooks.module.js
hooks.module.js
module.exports = angular
.module("app.hooks", [])
.factory("hooks", require("./hooks.factory.js"))
.config(require("./hooks.config.js"));
hooks.config.js
module.exports = ["$httpProvider", function($httpProvider) {
$httpProvider.interceptors.push("hooks");
}];
hooks.factory.js
module.exports = ["$q", "$location", function($q, $location) {
var basePrefix = "/api/v1";
return {
//invoked on every http request
request: function(request) {
request.url = interpret(request.url);
return $q.when(request);
}
};
function interpret(str) {
//if requesting an html template, don't do anything
if (str.indexOf(".html") > -1)
return str;
//if you're accessing the api, append the base prefix globally here
else
return basePrefix + str;
}
}];

Setting application wide HTTP headers in AngularJS

Is there a way to set the $httpProvider headers outside of angular.module('myApp', []).config()?
I'm getting an Auth-Token from the server after I login the user, and I need to add it as a HTTP Header to all following requests.
You can use default headers for angular 1.0.x:
$http.defaults.headers.common['Authentication'] = 'authentication';
or request interceptor for angular 1.1.x+:
myapp.factory('httpRequestInterceptor', function () {
return {
request: function (config) {
// use this to destroying other existing headers
config.headers = {'Authentication':'authentication'}
// use this to prevent destroying other existing headers
// config.headers['Authorization'] = 'authentication';
return config;
}
};
});
myapp.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpRequestInterceptor');
});
Since factories/services are singletons, this works as long as you do not need to dynamically change your 'authentication' value after the service has been instantiated.
$http.defaults.headers.common['Auth-Token'] = 'token';
It seems headers() normalizes the key names.
Adding to above responses of #Guria and #Panga
config.headers['X-Access-Token'] = $window.sessionStorage.token;
One can use x-access-token in header as JWT(jsonwebtoken).
I store JWT in the session storage when a user authenticate first time.

Categories