Angular Intercepting a particular http request and prompt for login - angularjs

I am working on an angular app. It's an SPA. I am loading the profile(or myAccount) page into the home page when user clicks on a link.
<a href="#" ng-click="getProfileData()"/></a>
in my controller:
$scope.getProfileData(){
$http.get('/profile').then(
function success(){
},
function error(){
}
}
}
the link makes an ajax request through $http service of angular.
what I want to do is, when user clicks on the link, before making the ajax request to check if he's logged in or not. if not, open the sign in popup, and after sign in continue with the same request.
I've looked into $httpProvider.interceptors, but I'm still not sure how to do it, since I need to pause hold the $http request till the login is completed, modify the config by adding login details into it and then continue.
Is using of interceptors the correct option? If yes, how should I proceed towards my objective?
EDIT:
my primary concern is to continue with the same request. i.e. i don't want the user to click on profile link again after he has logged in. i can check if user is logged in or not before making the ajax request. if he is, then it's good. but if he's not, he has to log in (in the modal i pop up), and then he has to click on the profile link again. is there a way to continue the previous http request after login?

There are only three points in the application where the login modal should appear:
When you are on a welcome page and you click “Login”.
When you are not logged in and you attempt to visit a page that requires login.
When you attempt to make a request that requires a login(Ex:session expiration).
Determining which pages require a logged in user
Considering you are using ui router,you can secure routes of your application with the help of attaching additional properties to a route.Here we add a requireLogin property for each state.
app.config(function ($stateProvider) {
$stateProvider
.state('welcome', {
url: '/welcome',
// ...
data: {
requireLogin: false
}
})
.state('app', {
abstract: true,
// ...
data: {
requireLogin: true // this property will apply to all children of 'app'
}
})
});
For routes that do not require a login we set requireLogin to false.
Capturing attempted state changes
This is where we are going to capture the attempted state changes and inspect them for our requireLogin property.
app.run(function ($rootScope) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
var requireLogin = toState.data.requireLogin;
if (requireLogin && typeof $rootScope.currentUser === 'undefined') {
event.preventDefault();
$rootScope.returnToState = toState.url;
// get me a login modal!
}
});
});
As you can see, if the route requires a login and $rootScope.currentUser is not yet set, we will prevent the attempted state change and show the modal.
Redirecting to initially requested url after login
The last part requires redirecting to the initially requested url after login.In our app.run we have set a variable $rootScope.returnToState = toState.url which we can use in our login controller to redirect.In our controller for the login page we can do something like this:
$scope.login = function(form) {
$scope.submitted = true;
if(form.$valid) {
Auth.login({
email: $scope.user.email,
password: $scope.user.password
})
.then( function() {
// Logged in, redirect to correct room
if( $rootScope.returnToState) {
$location.path($rootScope.returnToState);
} else {
//redirect all others after login to /rooms
$location.path('/home');
}
})
.catch( function(err) {
$scope.errors.other = err.message;
});
}
};
For further reference please refer to this blog post http://brewhouse.io/blog/2014/12/09/authentication-made-simple-in-single-page-angularjs-applications.html
This can help you build a rock solid authorization for your app which I think you might be looking for.

Why can't you just make another function call to verify, if the user is logged-in. And based on that fire up the ajax request that you are trying up there. Something like
$scope.getProfileData(){
if($scope.isLoggedin()){
$http.get('/profile').then(
function success(){
},
function error(){
}
}
}
};
$scope.isLoggedin = function(){
// Do some processing and return true/false based on if user is logged-in
};

Related

AngularJS $state.go not working if user not logged in

Below i have a state in my routes.js in my angular routes controller. I want it to check if the user is an admin, and only if it is an admin it goes to admin, otherwise it is redirected to home page.
There are no errors on the console if the user is logged in and is an admin, also it redirects if the user is not an admin and logged in, but if a user is not logged in and not an admin i can still see the template of that page and it says POST http://localhost:3000/users/sign_in.json 401 (Unauthorized)
.state('adminpage', {
url: '/adminpage',
templateUrl: 'views/adminpage.html',
controller: 'AdminPageCtrl',
onEnter: function(Auth, $state){
Auth.currentUser().then(function( user){
if (user.isadmin) {
$state.go('adminpage');
}
else {
$state.go('home');
}
})
}
})
I assume Auth.currentUser() rejects the promise if user is not logged in. So, you need to define what should be done on promise rejection, i.e. if user is not logged in.
onEnter: function(Auth, $state){
Auth.currentUser().then(function( user){
if (user.isadmin) {
$state.go('adminpage');
}
else {
$state.go('home');
}
})
.catch(function() {
// Redirect the user somewhere else, or show an error here.
});
}
The .catch() method actually handles the rejection of the promise, and you can try to redirect the user somewhere else in this block. In this way, the restricted page won't be shown to the user.
Hope that helps.

Handling secure login page in protractor

My team is working to use AngularJs and Polymer components for a new web app. I am looking into how to create a UI automation suite for this. After lots of research looks like Protractor may help me out here with some tweaks to handle Polymer. But, the current challenge is as follows -
I navigate to the app
As part of our company policy, the every web visit is validated (unless within same session). Here is how the validation works -
A login page (non-Anugular) page appears after one types the required url. Sign in with the credentials
Another intermediate page appears where it asks to wait for page to load or click a link to go to next page. Click the link
Url changes back to the original used in #1
Note: These validation pages take hell lot of time to load (changes to different internal urls). Also, the validation is skipped sometimes (within same session or through some other logic)
I have been struggling to design a prototype to handle all these. I am also trying to use Page Object while designing the prototype. Here is what I have so far.
login.js
________________________________________________________
var currentUrl;
var lastChangedUrl;
var secureUrl = 'corplogin.ssogen2.corporate.company.com';
var getwayUrl = 'gateway.zscalertwo.net';
var loginSuite = function(driver) {
var defer = protractor.promise.defer();
describe('Handle login', function() {
/*afterEach(function() {
//driver.manage().deleteAllCookies();
})*/
//it('Login to security test', function(){
//********** Wait for page to load/URL to change to secure login page ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
}).then(function() {
//********** login to secure page ************
if (lastChangedUrl.indexOf(secureUrl) > -1 || lastChangedUrl.indexOf(getwayUrl) > -1) {
var element = driver.findElement(By.name("username"));
element.sendKeys("Username");
element = driver.findElement(By.name("password"));
element.sendKeys("password"); //Give password
element = driver.findElement(By.name("submitFrm"));
element.click();
}
}).then (function() {
//********** page is slow. wait for page to load/URL to change ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
}).then (function() {
//********** Click on the link to to go to test page ***********
if (lastChangedUrl.indexOf(getwayUrl) > -1) {
var element = driver.findElement(By.tagName("a"));
console.log("before click............");
element.click();
}
//********** page is slow. wait for page to load/URL to change ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
})
.then (function() {
//return defer.promise;
//browser.pause();
});
}, 60000);
});
//});
}, 60000);
return defer.promise;
};
module.exports = loginSuite;
spec.js
___________________________________________________________________________
describe('Protractor Demo App', function() {
var myUrl = 'http://<my test app url>/';
var driver = browser.driver;
beforeEach(function() {
driver.get(myUrl);
});
it('should login', function() {
loginSuite(driver)
.then(
function(){
console.log("End of tests:");
expect(driver.getCurrentUrl()).toBe(myUrl);
});
});
The issue here -
My expectation here is to have the promise returns to spec.js after the secure login page is handled so that I can continue with other testing using the driver object. For the sake testing I am logging 'End of tests' message and doing a dummy validation. But, looks like those two lines don't get executed.
Login to the secure site works and I see page changes to original test page. I tested that with Browser.pause(). But, the logging 'End of test' never happens, nor the validation.
I need to handle the scenario where the secure login page doesn't appear. Not sure what adjustment I need to do in login.js page
Is my approach for page object and handling the promises wrong here? I am able to go to one step further on the test app page when all the code are placed under one js file instead of splitting them for page object. Please help here.
I wanted to share with you the "polymer way" of solving your problem.
The code below use two elements to monitor the URL, the auth flow, the previous page visited and log the user in/out of the app
The first will bind to the origin route, so you can send the user back there
<app-route
route="{{route}}"
pattern="/origin/:page"
data="{{data}}"
tail="{{subroute}}">
</app-route>
The second will bind to the authPage, allowing you to show/hide the auth page.
<app-route
route="{{subroute}}"
pattern=":authPage"
data="{{data}}
active="{{authPageActive}}">
</app-route>
User auth, monitoring and page redirecting
Use the element: <firebase-auth>
Is the user singned in?: signedIn="{{isSignedIn}}"
<firebase-auth id="auth" user="{{user}}" provider="google" on-
error="handleError" signedIn="{{isSignedIn}}"></firebase-auth>
Add an observer
observers: [
'_userSignedInStatus(isSignedIn)' // observe the user in/out
],
Add a Function
_userSignedInStatus: function (isSignedIn) {
if (isSignedIn === false) {
this.page = 'view404'; // redirect the user to another page
// import an element that cover the view
} else {
//send a log message to your database
}
}

redirecting a page with angular routing after successfully calling an api on express server

In a single page application using angular routing, how can I redirect a page after an api call. In my case, I want to redirect the user to the profile page after they have called the login api. So this is what I thought would work but it isn't.
On the client, main.js. I have the angular routing set up
app.config(function($routeProvider){ $routeProvider
//the home page display
.when('/', {
templateUrl: 'main.html',
controller: 'mainController'
})
.when('/login', {
templateUrl: 'login.html',
controller: 'loginController'
})
.when('/signUp', {
templateUrl: 'signUp.html',
controller: 'signUpController'
})
.when('/profile', {
templateUrl: 'profile.html',
//controller: 'mainController'
}); });
and from my controller I call the /login post api
app.controller('authController', function($scope, $http, $rootScope, $location){
$scope.user = {username: '', password: ''};
$scope.error_message = '';
$scope.login = function(){
$http.post('/login', $scope.user).success(function(data){
if(data.state == 'success'){
//set username authenticated property to true after successful log in
//I am only pasting some of my code here, more logic before controller
$rootScope.authenticated = true;
$rootScope.current_user = "james";
$location.path('/profile');
}
else{
$scope.error_message = data.message;
}
});
};
});
and here is my login api
router.post('/login', passport.authenticate('local-login', {
successRedirect : '/success', // redirect to the secure profile section
failureRedirect : '/failure', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
and when it succeeds, it calls success which sends back the data which should trigger the callback in $http.post and redirect the page through $location.path('/profile');. However, the callback isn't called and my page displays the user information from res.send
//sends successful login state back to angular
router.get('/success', function(req, res){
res.send({state: 'success', user: req.user ? req.user : null});
});
Am I on the right track? I am just following microsoft's tutorial https://www.microsoftvirtualacademy.com/en-us/training-courses/mean-stack-jump-start-8442 but their completed page on github doesn't even work so it doesn't help me debug this problem of mine.
Using successRedirect and failureRedirect in passport will redirect the client to the specified pages, which will prevent your client-side angularJS routing from taking place. The reason you're seeing the user info after logging in is because your client is being redirected to the /success page, rather than actually responding to the original request. The client then fetches the success page with a GET request, and the new GET request is then responded to with the user info.
I would suggest leaving the node.js redirects out when using AngularJS, since you probably want to handle redirection on the client side:
router.post('/login', passport.authenticate('local-login'), function(req, res){
res.send(req.user);
});
The inline function will never execute if the user is not authenticated. Instead, passport will respond directly with a 401 error status, with a body of "Unauthorized". Therefore the success state is not required. On the client side, you should use the .error() function to deal with 401 errors, rather than checking your state variable:
$http.post('/login', $scope.user).success(function(user){
$rootScope.authenticated = true;
$rootScope.current_user = "james";
$location.path('/profile');
})
.error(function(err){
$scope.error_message = err;
});
If you want to pass back a more specific reason as to why the request was unauthorized (which is not always a good idea), you can use flash messages, and issue another GET request from angular to get a more detailed authorization failure message.
You seem to have a slight impedance mismatch on what the front-end and back-end want to do here. Your AngularJS code expects to make a POST to the API endpoint and get back a 200 (success) along with some JSON data which tells it about the success or failure of the login attempt.
The back-end, thinks it's going to receive a POST and then redirect the caller to a new location. At least that's the way I'm reading it. It's not simply sending back some data with an HTTP response code of 200 or an error code.
I think you want to tweak the back-end code to simply return data to the front-end to get the result you expect.
So far I haven't seen success in making Ajax calls to API redirecting to a page. We have a similar situation where API call may result in redirecting to a error page. We wanted to handle that in the server rather than asking UI (Angular) to do it. But it's just frustrating to see none of the methods of redirect like res.redirect are working.
Our scenario is Angular makes a API call through Ajax and API running on Node.js should redirect to a html page.

Angular.js: page flickering on authentication redirect

I'm implementing some simple client-side authentication logic in Angular.js. The pages involved are:
/account#/login (public)
/account (require login)
/account#/settings (require login)
When a user is not logged in and try to visit either /account or /account/#/settings, the app is supposed to redirect to the login page.
I have the following routes configured using ui-router:
$stateProvider
.state('overview', {
url: '/',
restricted: true
})
.state('settings', {
url: '/settings',
restricted: true
})
.state('login', {
url: '/login',
restricted: false
})
and upon URL change, I check if the upcoming page is a restricted page and whether the current user is not logged in. If so redirect to login.
app.run(function($rootScope, $location, $state, auth) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.restricted && !auth.isLoggedIn()) {
event.preventDefault();
$state.go('login');
}
});
});
auth is just a service that checks the login status and returns either true (logged in) or false (not logged in).
Here's my question:
Even though this (kind of) works, I see a page flickering issue when trying to visit a restricted page while not logged in. The page flashes the contents of the restricted page quickly before redirecting me to the login page.
I did a little bit researching online and some people have mentioned the potential solution could be using resolve when defining my states, since the page won't load unless it resolves successfully. However, when I try to add
resolve: {
load: function(auth) {
return auth.isLoggedIn();
}
}
It didn't work. What am I missing? Is using resolve the way to go?
The way you are currently doing it will check if the user is logged in or not and set load to true or false. Also controller gets instantiated before load is resolved which is why you see the flickering. You need to achieve two things here:
Make sure that load is resolved before the controller is instantiated.
If user is not logged in, redirect the user to the login page.
For the first part we need to use a promise as it will be resolved and converted to value before controller is instantiated. This is what the documentation says:
If any of these dependencies are promises, they will be resolved and
converted to a value before the controller is instantiated and the
$stateChangeSuccess event is fired.
Following code can do that for us:
var isLoggedin = ['auth', '$q',
function(auth, $q) {
var deferred = $q.defer();
//assuming auth.isLoggedIn returns a promise
var loginPromise = auth.isLoggedIn();
loginPromise.then(
function(response) {
deferred.resolve(response);
},
function(error) {
deferred.reject('Not logged in');
});
return deferred.promise;
}
];
And states will use isLoggedin:
$stateProvider
.state('overview', {
url: '/',
resolve: {
loggedin: isLoggedin
}
})
.state('settings', {
url: '/settings',
resolve: {
loggedin: isLoggedin
}
})
.state('login', {
url: '/login'
})
For the second problem, that is redirecting the user to login page, you can listen to $stateChangeError event which is fired in case the state is not resolved, and use $state.go to redirect the user.

ui-router: Prevent resolve to be resolved when activating a state in one of the resolve

In the home state, I have a function that checks if the current user is authorized along with other promises.
resolve: {
authorize: ['AuthService', function (AuthService) {
console.log("authorize from home");
return AuthService.authorize();
}],
competitions: ['Competition', function (Competition) {
return Competition.query();
}]
}
The AuthService.authorize activates the login state if there is no user logged in.
var authorize = function () {
console.log("Authorize ");
if (!isLoggedIn()) {
$q.when(angular.noop).then(function () {
console.log("Not logged in, go to login state");
$state.go('login');
});
}
};
It's working as expected the login state is activated but the competitions promise is resolved (I can see the REST call returning a 401 if no user logged in which is correct). Since the authorize resolve activates another state, is there a way to prevent the following resolve to be executed?
My implementation is based on that question: angular ui-router login authentication

Resources