Tring to add interceptor header for my every request, however, it is giving me below error.
Uncaught Error: [$injector:cdep] Circular dependency found: $http <- Auth <- httpRequestInterceptor <- $http <- $templateRequest <- $route
app.js
var app= angular.module('myDemoApp',['ngRoute'])
app.factory('httpRequestInterceptor', ['Auth', function (Auth) {
return {
request: function (config) {
config.headers['x-access-token'] = Auth.getToken();
return config;
}
};
}]);
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpRequestInterceptor');
});
Auth Service
(function () {
'use strict';
myDemoApp.factory('Auth', ['$http', '$window', Auth]);
/******Auth function start*****/
function Auth($http, $window) {
var authFactory = {};
authFactory.setToken = setToken;
authFactory.getToken = getToken;
return authFactory;
/*setToken function start*/
function setToken(token) {
if (token) {
$window.localStorage.setItem('token', token);
} else {
$window.localStorage.removeItem('token');
}
}
/*getToken function start*/
function getToken() {
return $window.localStorage.getItem('token')
}
}
})();
You can't do this because.
You have created httpRequestInterceptor which intercepts all $http requests.
Now, you are passing Auth in the httpRequestInterceptor.
If you'll see, Auth uses $http request inside itself.
So, your interceptor can itself cause a http request using Auth.
Hence, its circular error and angularjs wont allow you to do that !
Remove $http from Auth factory OR dont insert a service into interceptor which itself uses $http.
I hope you got, how the infinite loop chain being created, hence a circular dependency error !
Related
I'm trying to write a test suite for an Angular app that uses Keycloak for authentication.
However, as Keycloak requires you to manually bootstrap Angular and set up a few interceptors, I'm unable to fire any test due to the following error:
Error: $injector:unpr
Unknown Provider: AuthProvider <- Auth <- authInterceptor <- $http <- $templateRequest <- $route
This is the code for the interceptor that raises the error:
angular.module('MPMReportGenerator')
.factory('authInterceptor', function authInterceptor ($q, Auth, $log) {
return {
request: function (config) {
var deferred = $q.defer()
Auth.updateToken(5).success(function () {
config.headers = config.headers || {}
config.headers.Authorization = 'Bearer ' + Auth.token
deferred.resolve(config)
}).error(function () {
deferred.reject('Failed to refresh token')
})
$log.info(deferred.promise)
return deferred.promise
}
}
})
My thinking is that I should mock the interceptor and just have it return the request.
However, I fail to see how I could do that, since this interceptor is never injected anywhere as a dependency, it's simply declared with the block above and that's it. My understanding of mocked services is that they need to be injected somewhere to be mocked.
My implementation of Keycloak into Angular comes straight from their examples, if that helps.
Edit
I've been trying to inject a mocked Auth module into the service I'm writing a test for, but still no change.
I'm very new to unit testing in general, so I'm a bit lost trying to track this down. I feel like I know where the issue is, but not how to solve it (The Auth service is added during the bootstrap of the app, I need to mock it for things to work, but it seems I don't know how/where to mock it properly)
Here's the whole testing code:
describe('Services', function () {
'use strict'
beforeEach(module('MPMReportGenerator'))
module(function ($provide) {
$provide.factory('Auth', function () {
return null
})
})
var sectionService, $httpBackend, mockAuth
beforeEach(inject(function (_sectionService_, _$httpBackend_, Auth) {
sectionService = _sectionService_
$httpBackend = _$httpBackend_
mockAuth = Auth
}))
it('should get sections', function () {
$httpBackend.expect('GET', '/MPMReportGenerator/api/categories/all').respond(200)
sectionService.getSections()
expect($httpBackend.flush).not.toThrow()
})
})
Edit 2
I've managed to get past my initial error by making a mock version of Auth.
I am now facing issues implementing a mock version of Keycloak's Javascript library.
My current mock code is as follow:
beforeEach(module(function ($provide) {
$provide.factory('Auth', function ($q) {
return {
updateToken: function (minValidity) {
return {
success: function (fn) {
var deferred = $q.defer()
deferred.resolve('')
fn(deferred.promise)
},
error: function (fn) {
var deferred = $q.defer()
deferred.resolve('Error')
fn(deferred.promise)
}
}
},
token: 'thisisafaketokenfortesting'
}
})
}))
And throws this error:
Expected function not to throw, but it threw TypeError: undefined is not an object (near '...}).error(function () {...').
target/MPMReportGenerator-1.0.0/js/app.service.spec.js:42:43
loaded#http://localhost:9876/context.js:151:17
My actual test is this:
it('should get sections', function () {
$httpBackend.expect('GET', '/MPMReportGenerator/api/categories/all').respond(200)
sectionService.getSections()
expect($httpBackend.flush).not.toThrow()
})
I finally figured it out.
Here is the needed code if anyone wants to test an Angular app with keycloak:
beforeEach(
module(function ($provide) {
$provide.factory('Auth', function ($q) {
return {
updateToken: function (minValidity) {
return {
success: function () {
return {
error: function () {
var deferred = $q.defer()
return deferred.promise
}
}
}
}
},
token: 'thisisafaketokenfortesting'
}
})
}))
Note that you will likely need to mock other parts of the keycloak library if you intend to test the interceptors provided in the official examples.
Edit
Don't use the code above, the following works much better:
$provide.factory('Auth', function () {
return {
updateToken: function (minValidity) {
return {
success: function () {
return this
},
error: function () {
return this
}
}
},
token: 'thisisafaketokenfortesting'
}
})
I try to use common provider and in angular app config function set data in provider.
.provider('userData', function() {
var authUser = {};
return {
checkUser: function() {
// wish to able use $http here for request to get data from service
// save all data into 'authUser' object
// $get return 'authUser' object
},
getCookie: function(value) {
// wish to able use $http here
},
$get: function() {
// only for return object
return authUser;
}
}
})
app.config(['userDataProvider', function(userDataProvider) {
userDataProvider.checkUser();
});
.controller('headerCtrl', ['$scope', 'userData', '$http', function($scope, userData, $http) {
// use inside all controllers/directives 'userData'
});
I try to use $http as parameter in $get function -> not working: error:
$get: function($http) {
return authUser;
}
Also I can't find any valid example for using $http inside provider. Inside service/factory $http work fine, but I need to prepare data in provider from config function.
Services, factory & value aren't available at config phase by design.
You could do it in run()
app.run(['userData', function(userData) {
userData.checkUser();
});
Where do i place the common handleError and handleSuccess functions.
These are common functions that will be used by every service. where is the best place to put these functions. Should i put theses as global functions and inject them as dependency.
(function () {
"use strict";
angular.module('myApp.Group')
.service('GroupService', function ($http, $q, $location, Environment) {
// I transform the error response, unwrapping the application dta from
// the API response payload.
function handleError(response) {
// The API response from the server should be returned in a
// nomralized format. However, if the request was not handled by the
// server (or what not handles properly - ex. server error), then we
// may have to normalize it on our end, as best we can.
if (!angular.isObject(response.data) || !response.data.message) {
return ($q.reject("An unknown error occurred."));
}
// Otherwise, use expected error message.
return ($q.reject(response.data.message));
}
// I transform the successful response, unwrapping the application data
// from the API response payload.
function handleSuccess(response) {
return (response.data);
}
this.remove = function (id) {
var request = $http({
method: "delete",
url: '/group/' + id
});
return (request.then(handleSuccess, handleError));
};
});
}());
Here's how we do it:
We use the $httpProvider to intercept the responses and deal with each code on their own. We made a service to handle this functionality.
Our app config looks like this:
appModule.config(['$routeProvider', '$locationProvider', '$httpProvider', '$provide',
function ($routeProvider, $locationProvider, $httpProvider, $provide) {
// Http interceptor to handle session timeouts and basic errors
$httpProvider.responseInterceptors.push(['httpHandlersSrv', function (httpHandlersSrv) {
return function (promise) { return promise.then(httpHandlersSrv.success, httpHandlersSrv.error); };
}]);
routeProvider = $routeProvider;
$locationProvider.html5Mode(true);
}
]);
This is what our $httpHandlersSrv looks like where we handle errors. Notice we just pass the successful responses along without doing anything:
angular.module('appModule').factory('httpHandlersSrv', ['$q', '$location', '$rootScope', 'toaster', '$window', function ($q, $location, $rootScope, toaster, $window) {
return {
success: function (response) {
return response;
},
error: function (response) {
switch (response.status) {
case 0:
//Do something when we don't get a response back
break;
case 401:
//Do something when we get an authorization error
break;
case 400:
//Do something for other errors
break;
case 500:
//Do something when we get a server error
break;
default:
//Do something with other error codes
break;
}
return $q.reject(response);
}
};
}]);
I need to do a request inside the RUN method to retrieve de user data from an api.
The first page (home), depends on the user data.
This is the sequence of dispatchs in my console:
CONFIG
RUN
INIT GET USER DATA
SIDEBAR
HOME
SUCCESS GET USER DATA
My problem is, i need to wait user data before call sidebar and home (controller and view) and i don't know how can i do this.
UPDATE
I have this until now:
MY CONFIG:
extranet.config(['$httpProvider', '$routeProvider', function ($httpProvider, $routeProvider) {
// My ROUTE CONFIG
console.log('CONFIG');
}]);
My RUN:
extranet.run(function($rootScope, $location, $http, Cookie, Auth, Session) {
console.log('RUN');
var token = Cookie.get('token');
// The login is done
var success = function (data) {
Session.create(data);
console.log('USER DATA SUCCESS');
};
var error = function () {
$location.path('/login');
};
// GET USER DATA
Auth.isAuthenticated().success(success).error(error);
});
MY CONTROLLER MAIN:
extranet.controller('MainCtrl', function ($scope, $location) {
console.log('MAIN CONTROLLER');
});
By using resolver
extranet.config(['$httpProvider', '$routeProvider', function ($httpProvider, $routeProvider) {
// My ROUTE CONFIG
$routeProvider.when('/', {
templateUrl: "/app/templates/sidebar.html",
controller: "siderbarController",
title: "EventList",
resolve: {
events: function ($q, Cookie,Session) {
var deffered = $q.defer();
Cookie.get('token').$promise
.then(function (events) {
Session.create(data);
console.log('USER DATA SUCCESS');
deffered.resolve(events);
}, function (status) {
deffered.reject(status);
});
return deffered.promise;
}
}
}]);
I hope you get some idea.
If you are using AngularJS methods for server requests you will get a promise. A promise gets resolved as soon as the response is recieved. All defined callbacks "wait" until the resolve.
Naive solution
So, you will use $http or even $resource if you have a REST-like backend:
var promise = $http.get(userDataUrl, params)
$rootScope.userDataPromise = promise;
After that you can use that promise whereever you need the data:
$rootScope.userDataPromise.then(myCallback)
Better solution
Using $rootScope for that purpose is not an elegant solution though. You should encapsulate the Userdata stuff in a service and inject it whereever you need it.
app.factory('UserData', ['$http',
function($http) {
var fetch = function() {
return $http.get(userDataUrl, params)
};
return {
fetch: fetch
};
}
]);
Now you can use that service in other modules:
app.controller('MainCtrl', ['$scope', 'UserService',
function ($scope, UserService) {
var update = function(response) {
$scope.userData = response.userData;
}
var promise = UserService.fetch();
promise.then(update)
}
);
I'm trying to write a HTTP interceptor for my AngularJS app to handle authentication.
This code works, but I'm concerned about manually injecting a service since I thought Angular is supposed to handle this automatically:
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($location, $injector) {
return {
'request': function (config) {
//injected manually to get around circular dependency problem.
var AuthService = $injector.get('AuthService');
console.log(AuthService);
console.log('in request interceptor');
if (!AuthService.isAuthenticated() && $location.path != '/login') {
console.log('user is not logged in.');
$location.path('/login');
}
return config;
}
};
})
}]);
What I started out doing, but ran into circular dependency problems:
app.config(function ($provide, $httpProvider) {
$provide.factory('HttpInterceptor', function ($q, $location, AuthService) {
return {
'request': function (config) {
console.log('in request interceptor.');
if (!AuthService.isAuthenticated() && $location.path != '/login') {
console.log('user is not logged in.');
$location.path('/login');
}
return config;
}
};
});
$httpProvider.interceptors.push('HttpInterceptor');
});
Another reason why I'm concerned is that the section on $http in the Angular Docs seem to show a way to get dependencies injected the "regular way" into a Http interceptor. See their code snippet under "Interceptors":
// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
// optional method
'request': function(config) {
// do something on success
return config || $q.when(config);
},
// optional method
'requestError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
},
// optional method
'response': function(response) {
// do something on success
return response || $q.when(response);
},
// optional method
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
};
}
});
$httpProvider.interceptors.push('myHttpInterceptor');
Where should the above code go?
I guess my question is what's the right way to go about doing this?
Thanks, and I hope my question was clear enough.
This is what I ended up doing
.config(['$httpProvider', function ($httpProvider) {
//enable cors
$httpProvider.defaults.useXDomain = true;
$httpProvider.interceptors.push(['$location', '$injector', '$q', function ($location, $injector, $q) {
return {
'request': function (config) {
//injected manually to get around circular dependency problem.
var AuthService = $injector.get('Auth');
if (!AuthService.isAuthenticated()) {
$location.path('/login');
} else {
//add session_id as a bearer token in header of all outgoing HTTP requests.
var currentUser = AuthService.getCurrentUser();
if (currentUser !== null) {
var sessionId = AuthService.getCurrentUser().sessionId;
if (sessionId) {
config.headers.Authorization = 'Bearer ' + sessionId;
}
}
}
//add headers
return config;
},
'responseError': function (rejection) {
if (rejection.status === 401) {
//injected manually to get around circular dependency problem.
var AuthService = $injector.get('Auth');
//if server returns 401 despite user being authenticated on app side, it means session timed out on server
if (AuthService.isAuthenticated()) {
AuthService.appLogOut();
}
$location.path('/login');
return $q.reject(rejection);
}
}
};
}]);
}]);
Note: The $injector.get calls should be within the methods of the interceptor, if you try to use them elsewhere you will continue to get a circular dependency error in JS.
You have a circular dependency between $http and your AuthService.
What you are doing by using the $injector service is solving the chicken-and-egg problem by delaying the dependency of $http on the AuthService.
I believe that what you did is actually the simplest way of doing it.
You could also do this by:
Registering the interceptor later (doing so in a run() block instead of a config() block might already do the trick). But can you guarantee that $http hasn't been called already?
"Injecting" $http manually into the AuthService when you're registering the interceptor by calling AuthService.setHttp() or something.
...
I think using the $injector directly is an antipattern.
A way to break the circular dependency is to use an event:
Instead of injecting $state, inject $rootScope.
Instead of redirecting directly, do
this.$rootScope.$emit("unauthorized");
plus
angular
.module('foo')
.run(function($rootScope, $state) {
$rootScope.$on('unauthorized', () => {
$state.transitionTo('login');
});
});
Bad logic made such results
Actually there is no point of seeking is user authored or not in Http Interceptor. I would recomend to wrap your all HTTP requests into single .service (or .factory, or into .provider), and use it for ALL requests. On each time you call function, you can check is user logged in or not. If all is ok, allow send request.
In your case, Angular application will send request in any case, you just checking authorization there, and after that JavaScript will send request.
Core of your problem
myHttpInterceptor is called under $httpProvider instance. Your AuthService uses $http, or $resource, and here you have dependency recursion, or circular dependency. If your remove that dependency from AuthService, than you will not see that error.
Also as #Pieter Herroelen pointed, you could place this interceptor in your module module.run, but this will be more like a hack, not a solution.
If your up to do clean and self descriptive code, you must go with some of SOLID principles.
At least Single Responsibility principle will help you a lot in such situations.
If you're just checking for the Auth state (isAuthorized()) I would recommend to put that state in a separate module, say "Auth", which just holds the state and doesn't use $http itself.
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($location, Auth) {
return {
'request': function (config) {
if (!Auth.isAuthenticated() && $location.path != '/login') {
console.log('user is not logged in.');
$location.path('/login');
}
return config;
}
}
})
}])
Auth Module:
angular
.module('app')
.factory('Auth', Auth)
function Auth() {
var $scope = {}
$scope.sessionId = localStorage.getItem('sessionId')
$scope.authorized = $scope.sessionId !== null
//... other auth relevant data
$scope.isAuthorized = function() {
return $scope.authorized
}
return $scope
}
(i used localStorage to store the sessionId on client side here, but you can also set this inside your AuthService after a $http call for example)