Angular http interceptors configuration - angularjs

I am building a project in ionic and I need to send jwt tokens with each request. I am a total angular newbie and I wonder where I need to place the logic for http interceptors. Where should I do that, should I put that inside config part, make a new service or something else?
This is the interceptor logic I need to insert:
$httpProvider.interceptors.push(['$q', '$location', '$localStorage', function ($q, $location, $localStorage) {
return {
'request': function (config) {
config.headers = config.headers || {};
if ($localStorage.token) {
config.headers.Authorization = 'Bearer ' + $localStorage.token;
}
return config;
},
'responseError': function (response) {
if (response.status === 401 || response.status === 403) {
$location.path('/signin');
}
return $q.reject(response);
}
};
}]);
This is the config part in my app.js:
.config(function($stateProvider, $urlRouterProvider, $authProvider, ApiEndpoint) {
$authProvider.loginUrl = ApiEndpoint.url + '/authenticate';
$stateProvider
.state('main', {
url: '/main',
abstract: true,
templateUrl: 'templates/main.html'
})
.state('main.auth', {
url: '/auth',
views: {
'content': {
templateUrl: 'templates/login.html',
controller: 'AuthController'
}
}
})
.state('main.front', {
url: '/front',
views: {
'content': {
templateUrl: 'templates/main-front.html',
controller: 'FrontPageController'
}
}
})
.state('main.article', {
url: '/article/{id}',
views: {
'content': {
templateUrl: 'templates/main-article.html',
controller: 'ArticleController'
}
}
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/main/front');
});
I have added it to services.js like this, I wonder if this is the right approach?
Updated code
services.js
angular.module('coop.services', [])
.factory('ArticleService', function($http, ApiEndpoint) {
return {
all: function() {
return $http.get(ApiEndpoint.url + "/articles/latest").then(function(response){
articles = response.data;
return articles;
});
},
get: function(id) {
return this.all().then(function(response) {
var articles = response;
for (var i in articles) {
if (articles[i].id == id) {
return articles[i];
}
}
return {};
})
}
};
})
.factory('AuthenticationInterceptor', function RequestInterceptor($q, $location, $localStorage, $rootScope, CoreConfig) {
var service = this;
service.request = function (config) {
config.headers = config.headers || {};
if ($localStorage.token) {
config.headers.Authorization = 'Bearer ' + $localStorage.token;
}
return config;
};
service.responseError = function (response) {
if (response.status === 401 || response.status === 403) {
$location.path('/signin');
}
return $q.reject(response);
};
return service;
});
.config part in app.js:
.config(function($stateProvider, $urlRouterProvider, $authProvider, ApiEndpoint, $httpProvider) {
$httpProvider.interceptors.push('AuthenticationInterceptor');
$authProvider.loginUrl = ApiEndpoint.url + '/authenticate';
$stateProvider
.state('main', {
url: '/main',
abstract: true,
templateUrl: 'templates/main.html'
})
.state('main.auth', {
url: '/auth',
views: {
'content': {
templateUrl: 'templates/login.html',
controller: 'AuthController'
}
}
})
.state('main.front', {
url: '/front',
views: {
'content': {
templateUrl: 'templates/main-front.html',
controller: 'FrontPageController'
}
}
})
.state('main.article', {
url: '/article/{id}',
views: {
'content': {
templateUrl: 'templates/main-article.html',
controller: 'ArticleController'
}
}
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/main/front');
});

The Interceptors are usually configured at the bootstrapping phase.
I tend to handle it under the app config:
appName.config(["$httpProvider", ($httpProvider: ng.IHttpProvider) => {
$httpProvider.interceptors.push(() => {
// Your interceptor's logic here
});
});

"An interceptor is simply a factory() service that returns an object with 4 properties that map to functions". So write your interceptor as a normal service with which methods you need overide (request, response, requestError, responseError).
In code example below, I just care to request and respondError property so I just return a service with two propertis. You can also made many interceptors to handle each kind of these property. Many interceptors can be applied to only one property (some kind of interceptor: authentication, handle error, restore request, pre-process response/request data...).
app.factory('AuthenticationInterceptor', function RequestInterceptor($rootScope, CoreConfig) {
var service = this;
service.request = function (config) {
if (angular.isDefined(CoreConfig.TokenKeyString) && angular.isDefined(CoreConfig.SessionKeyString)) {
config.headers['Authorization-Token'] = CoreConfig.TokenKeyString;
config.headers.SessionKeyString = CoreConfig.SessionKeyString;
}
return config;
};
service.responseError = function (response) {
return response;
};
return service;
});
then push your interceptor at config phase:
appp.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('AuthenticationInterceptor');
}]);

Related

Refactor angular ui-router resolver to use it globally

I have resolve method inside angular config. It was written to protect the view from unauthorized access. Now the problem is, if I create a different route file, I have to copy the same resolve on each file. Is there any other way so that I can write it once and use it everywhere?
(function(){
'use strict';
var app = angular.module('app');
app.config(/* #ngInject */ function($stateProvider, $urlRouterProvider) {
var authenticated = ['$q', 'MeHelper', '$state', function ($q, MeHelper, $state) {
var deferred = $q.defer();
MeHelper.ready()
.then(function (me) {
if (me.isAuthenticated()) {
deferred.resolve();
} else {
deferred.reject();
$state.go('login');
}
});
return deferred.promise;
}];
$stateProvider
.state('index', {
url: "",
views: {
"FullContentView": { templateUrl: "start.html" }
}
})
.state('dashboard', {
url: "/dashboard",
views: {
"FullContentView": { templateUrl: "dashboard/dashboard.html" }
},
resolve: {
authenticated: authenticated
}
})
$urlRouterProvider.otherwise('/404');
});
})();
Edit: MeHelper is a Service.
To refactor your code, you should register a service and take the authentication code to the service.
Authenticate service:
app.factory('authenticateService', ['$q', 'MeHelper',
function($q,MeHelper){
var obj = {};
obj.check_authentication = function(params)
{
var deferred = $q.defer();
MeHelper.ready()
.then(function (me) {
if (me.isAuthenticated()) {
deferred.resolve();
} else {
deferred.reject();
$state.go('login');
}
});
return deferred.promise;
}
return obj;
}
]);
Then, use this service in any route file in resolve, taking this service name in dependency injection or the function parameter,
Route configuration file:
(function(){
'use strict';
var app = angular.module('app');
app.config(/* #ngInject */ function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('index', {
url: "",
views: {
"FullContentView": { templateUrl: "start.html" }
}
})
.state('dashboard', {
url: "/dashboard",
views: {
"FullContentView": { templateUrl: "dashboard/dashboard.html" }
},
resolve: {
authenticated:
function(authenticateService) {
return authenticateService.check_authentication();
}
}
})
$urlRouterProvider.otherwise('/404');
});
})();
watch the below lines, this is what we changes in the route configuration to resolve.
the service is injected in below lines:
resolve: {
authenticated:
function(authenticateService) {
return authenticateService.check_authentication();
}
}
Do your check on route change.
app.run(function ($rootScope, $state) {
$rootScope.$on('$locationChangeSuccess', function () {
if (unauthorized && $state.current.name !== 'login') {
$state.go('login');
}
});
});

Angularjs $location.path('...') doesn't work

I'm working on authentication with angularjs, so after connecting my user I want to redirect him to the home page:
$scope.submit = function(user) {
var request = {
method: 'POST',
url: 'http://localhost:9001/signIn',
headers: {
'Content-Type': 'application/json'
},
data: {
"email": user.email,
"password": user.password,
"rememberMe": true
}
};
$http(request).then(function(data) {
$location.path('/home');
}, function(error) {
console.log(error);
});
};
here is my configuration:
app.config(function($urlRouterProvider, $stateProvider, $httpProvider, $authProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home', {
url: '/home',
templateUrl: '/home',
controller: 'HomeCtrl',
resolve: {
authenticated: function($q, $location, $auth) {
var deferred = $q.defer();
if (!$auth.isAuthenticated()) {
$location.path('/signIn');
} else {
deferred.resolve();
}
return deferred.promise;
}
}
})
.state('signIn', {
url: '/signIn',
templateUrl: '/signIn',
controller: 'SignInCtrl'
});
});
I tried this:
$http(request).then(function(data) {
$scope.$evalAsync(function() {
$location.path('/home');
});
console.log(data);
}, function(error) {
console.log(error);
});
also :
$location.path('/home');
$location.replace();
Neither of the above work, any help is greatly appreciated.
The home state resolver function fails to resolve or reject the $q.defer promise when $auth.isAuthenticated() returns false. This will cause the promise to hang and create a memory leak.
//ERRONEOUS CODE
$stateProvider
.state('home', {
url: '/home',
templateUrl: '/home',
controller: 'HomeCtrl',
resolve: {
authenticated: function($q, $location, $auth) {
var deferred = $q.defer();
if (!$auth.isAuthenticated()) {
$location.path('/signIn');
//FAILS to resolve or reject promise
} else {
deferred.resolve();
}
return deferred.promise;
}
}
})
Instead return a rejection when not authenticated:
$stateProvider
.state('home', {
url: '/home',
templateUrl: '/home',
controller: 'HomeCtrl',
resolve: {
authenticated: function($q, $location, $auth) {
//var deferred = $q.defer();
if ($auth.isAuthenticated()) {
return $q.resolve("AUTHENTICATED");
};
//otherwise
return $q.reject("NOT AUTHENTICATED");
})
}
})
When a resolver function returns a rejected promise, the state change will be prevented and the $stateChangeError event will be broadcast on $rootScope.

Middleware type functionality of Laravel in AngularJS?

I am new to AngularJS I have been recently deployed to a AngularJS project. Now at backend I use Laravel and I want to know if there is a way in Angular that I can create or use the same functionality what middleware does in Laravel.
What I basically want is that I need if user is logged in then only he can move forward in the application to other pages.
I have tried something but I don't feel it is correct.
app.controller('afterLogin', function ($scope, $http, $translate,$state,toastr,$auth) {
var init = function () {
var req = {
method: 'GET',
url: baseUrl + 'username',
headers: {
'X-CSRF-TOKEN': token
}
};
$http(req)
.then(function (response) {
if(response.data.name != '')
{
$state.go('app.dashboard');
}
else
{
$state.go('core.login');
}
});
};
// and fire it after definition
init();
$scope.login = function($Login_form) {
var req = {
method: 'POST',
url: baseUrl + 'login/check',
headers: {
'X-CSRF-TOKEN': token
},
data: $scope.user
};
$http(req)
.then(function (response) {
if(response.data.status == 'success')
{
$state.go('app.dashboard');
toastr.success('', 'Logged in successfully');
}else
{
toastr.warning('', response.data.message);
}
});
};
Now the init() does is that it goes to my Laravel controller and checks is user is logged in if not then returns to login page else other wise to dashboard.
I think it is a common problem, I had to solve it too.
Basically you sohuld use the ui-router and then for the states where authentication is needed, you can call an authentication function to proof if the user is logged or not (I also check if it has the roles a to access).
I suggest the use of a service to store the User state.
myApp.config(function($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /home
$urlRouterProvider.otherwise("/home");
//
// Now set up the states
$stateProvider
.state('home', {
url: "/home",
templateUrl: "partials/home.html",
controller: 'homeController',
controllerAs: 'homeCtrl'
})
.state('contact', {
url: "/contact",
templateUrl: "partials/contact.html",
controller: 'contactController',
controllerAs: 'contactCtrl'
})
.state('profile', {
url: '/profile',
templateUrl: 'partials/profile.html',
controller: 'profileCtrl',
resolve: {
authenticated: authentic
}
});
function authentic($q, $location, $auth) {
var deferred = $q.defer();
if (!$auth.isAuthenticated()) {
$location.path('/login');
} else {
deferred.resolve();
}
return deferred.promise;
};
});
I hope it helps.

Yeoman Angular Fullstack - how to secure routes?

I have created a new route 'Rooms' with the generator and have modelled my $stateProvider on the admin route
.state('admin', {
url: '/admin',
templateUrl: 'app/admin/admin.html',
controller: 'AdminController',
controllerAs: 'admin',
authenticate: 'admin'
});
Vs
$stateProvider
.state('rooms', {
url: '/rooms',
templateUrl: 'app/rooms/rooms.html',
controller: 'RoomsCtrl',
controllerAs: 'rooms',
authenticate: 'admin'
});
But my route still appears without authentication!
I guess I am missing a few things to make it secure, though I am unable to understand what!
Can anyone help?
Thanks
your controller should be like:
angular.module('auth8App').controller('RoomsCtrl', function ($scope,Auth,$location) {
//check if the user is logged-in
Auth.isLoggedInAsync(function(loggedIn) {
if (!loggedIn) {
//if the user is not logged Redirect to login
event.preventDefault();
$location.path('/login');
}
});
$scope.message = 'Hello';
});
check if you have an interceptor service factorie defined and called in your app.js lik this:
.config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);
$httpProvider.interceptors.push('authInterceptor');
})
.factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
return {
// Add authorization token to headers
request: function (config) {
config.headers = config.headers || {};
if ($cookieStore.get('token')) {
config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
}
return config;
},
// Intercept 401s and redirect you to login
responseError: function(response) {
if(response.status === 401) {
$location.path('/login');
// remove any stale tokens
$cookieStore.remove('token');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
};
})
.run(function ($rootScope, $location, Auth) {
// Redirect to login if route requires auth and you're not logged in
$rootScope.$on('$stateChangeStart', function (event, next) {
Auth.isLoggedInAsync(function(loggedIn) {
if (next.authenticate && !loggedIn) {
event.preventDefault();
$location.path('/login');
}
});
});
});

How to restrict routes with $routeProvider

In a MEAN app, I have an authService module with an Auth factory which contains an authFactory.isLoggedIn function:
// check if a user is logged in
// checks if there is a local token
authFactory.isLoggedIn = function() {
if (AuthToken.getToken())
return true;
else
return false;
};
So I thought I could use this with the resolve property of $routeProvider like this:
var MyModule = angular.module('app.routes', ['ngRoute']);
MyModule.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl : 'app/views/pages/home.html'
})
// login page
.when('/login', {
templateUrl : 'app/views/pages/login.html',
controller : 'mainController',
controllerAs: 'login'
})
// register page
.when('/register', {
templateUrl: 'app/views/pages/register.html',
controller: 'userCreateController',
controllerAs: 'register'
})
// upload page
.when('/upload', {
templateUrl : 'app/views/pages/upload.html',
controller: 'uploadController',
controllerAs: 'userupload',
resolve: function($q, $location) {
var deferred = $q.defer();
deferred.resolve();
if (!Auth.isLoggedIn) {
$location.path('/login');
}
return deferred.promise;
}
})
//logout
.otherwise({redirectTo: '/'});
$locationProvider.html5Mode(true);
}]);
Unfortunately this doesn't work to stop unauthenticated users accessing the upload page and I don't see any errors being reported.
I have seen instances of simpler ways to do this eg:
.when('/upload', {
templateUrl : 'app/views/pages/upload.html',
controller: 'uploadController',
controllerAs: 'userupload',
isLoggedIn: true
})
But that doesn't work either, which is a shame as it's far simpler.
In the end I was determined to use the resolve property of $routeProvider so after experimenting with the solution on http://midgetontoes.com/blog/2014/08/31/angularjs-check-user-login
I came up with:
var MyModule = angular.module('app.routes', ['ngRoute']);
MyModule.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
var onlyLoggedIn = function($location, $q, Auth) {
var deferred = $q.defer();
if (Auth.isLoggedIn()) {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
return deferred.promise;
};
$routeProvider
// route for the home page
.when('/', {
templateUrl : 'app/views/pages/home.html'
})
// login page
.when('/login', {
templateUrl : 'app/views/pages/login.html',
controller : 'mainController',
controllerAs: 'login'
})
// register page
.when('/register', {
templateUrl: 'app/views/pages/register.html',
controller: 'userCreateController',
controllerAs: 'register'
})
// upload page
.when('/upload', {
templateUrl : 'app/views/pages/upload.html',
controller: 'uploadController',
controllerAs: 'userupload',
resolve:{loggedIn:onlyLoggedIn}
})
//logout
.otherwise({redirectTo: '/'});
$locationProvider.html5Mode(true);
}]);
I am sure this isn't as good as the custom http interceptor as posited by #Dimitiri Algazin or as simple as the solution from #Pasan Ratnayake but it does fulfil my quest to use resolve. Thanks to #Dimitri and #Pasan anyway.
Add custom http interceptor. This is not exact code, just algorithm, some syntax might missing:
.factory('myHttpInterceptor', function($q, $location, AuthToken) {
function isLoggedIn() {
return !!AuthToken.getToken();
}
function canRecover(response) {
var status = response.status;
var config = response.config;
var method = config.method;
var url = config.url;
console.log("--->>> ", method, status, url);
if (status == 401) {
alert("401");
} else if ( status == 403) {
alert("403");
} else if (status == 404) {
alert("404");
} else if (status == 405) {
alert("405");
} else if (status == 500) {
alert("500");
} else {
}
return response;
}
return {
// optional method
'request': function(config) {
if (isLoggedIn()) {
return config;
} else {
$location.path('login');
}
},
// optional method
'response': function(response) {
// do something on success
return response;
},
// optional method
'requestError': function(rejection) {
return $q.reject(canRecover(rejection));
},
// optional method
'responseError': function(rejection) {
return $q.reject(canRecover(rejection));
}
};
})
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('myHttpInterceptor');
}])
There are multiple ways you could achieve this functionality.
Easiest would be to add a check similar to below code to each controller that you don't want your users to access.
// You could re-direct the user to a '401' state as well
if (!authFactory.isLoggedIn())
$state.go('login');

Resources