Delaying template rendering for authentication check in Angular - angularjs

Angular can cause a big security issue if you have static URL's that lead to content in conjunction with an authentication scheme. If an unregistered user visits the page, the content will flash on screen for a moment before redirecting to a safe location. I found this issue, searched extensively, and settled upon the resolve property in $routeConfig. Unfortunately, no matter what I do, it doesn't work. Code below:
$routeProvider.when('/dashboard/:id', {
templateUrl: 'js/partials/dashboard.html',
controller:'DashCtrl',
access: access.user,
resolve: {
login: function(authService) {
var promise = authService.isLoggedIn();
promise.then(function(data){
// We're in successfully
}).catch(function(err){
// ew, go away, redirect to login page
window.location = "login";
});
}
}
});
AuthService.isLoggedIn() looks like:
isLoggedin : function() {
return $http.get("users/session_check").then(function(result) {
if (result.data) {
if (user == undefined)
{
placeholderService.populatePlaceholders();
user = result.data;
}
return result.data;
} else {
return $q.reject("Please log in.");
}
});
}
No matter what, the view flashes. I have ng-cloaks on the page, nothing. I have no idea what else to do, but this has become downright infuriating.

The function that you pass to $routeProvider should return a promise. e.g.:
$routeProvider.when('/dashboard/:id', {
templateUrl: 'js/partials/dashboard.html',
controller:'DashCtrl',
access: access.user,
resolve: {
login: function(authService) {
var promise = authService.isLoggedIn();
return promise.then(function(data){
// We're in successfully
return data; // This gets injected as "login"
}).catch(function(err){
// ew, go away, redirect to login page
window.location = "login";
});
}
}
});
And, if I recall correctly, the value that the promise is fulfolled with ends up being inject-able into the controller as a service (in this case, the login service is whatever your promise gets fulfilled with).

Related

AngularJS: How to check auth from API using Transition Hooks of UI-Router?

How do I make the transition hook wait for the checkAuth() request from my API to fail before it would redirect the user to the login page without the transition hook successfully resolving?
This is the transition hook code I have:
app.js
angular.module('app')
.run(function ($state, $transitions, AuthService) {
$transitions.onBefore({ to: 'auth.**' }, function() {
AuthService.isAuthenticated().then(function (isAuthenticated) {
if (!isAuthenticated) {
$state.go('login');
}
});
});
});
I'm using the $state service to redirect the user to the login page when the unauthenticated user tries to access an auth restricted view. But with this implementation, the transition onBefore() is already resolved, so the transition is succeeding before my checkAuth() method finishes. So it's still showing the view its going to for a (split) sec, before it transitions to the login view.
Here is the implementation of the auth service methods used in the code above:
auth.service.js
authService.isAuthenticated = function () {
// Checks if there is an authenticated user in the app state.
var authUser = AuthUserStore.get();
if (authUser) {
return Promise.resolve(true);
}
// Do check auth API request when there's no auth user in the app state.
return authService.checkAuth()
.then(function () {
return !!AuthUserStore.get();
});
};
authService.checkAuth = function () {
return $http.get(API.URL + 'check-auth')
.then(function (res) {
// Store the user session data from the API.
AuthUserStore.set(res.data);
return res;
});
};
As per my understanding of the onBefore hook,
The return value can be used to pause, cancel, or redirect the current
Transition.
https://ui-router.github.io/ng1/docs/latest/classes/transition.transitionservice.html#onbefore
Perhaps you need to look into HookResult and use it to suit you're needs.
https://ui-router.github.io/ng1/docs/latest/modules/transition.html#hookresult
Hope this helps
Cheers!
Kudos to Jonathan Dsouza for providing the HookResult documentation from UI Router.
This issue is resolved by handling the Promise from the isAuthenticated() service method, and returning the necessary HookResult value to handle the transition as required:
app.js
angular.module('app')
.run(function ($transitions, AuthService) {
$transitions.onBefore({to: 'auth.**'}, function (trans) {
return AuthService.isAuthenticated().then(function (isAuthenticated) {
if (!isAuthenticated) {
return trans.router.stateService.target('login');
}
return true;
});
});
});

location on change view issue angularjs

Stuck in an issue where i have to check authentication when location changes:
Issue:
while on location change i request the back-end to check auth if response is not 401 then show the page otherwise redirect to login page, when response is 401 the page shows for a bit and then the redirection happens to login page , for me its a headache because the page shows for a bit and then location changes , someone help me please?
$rootScope.$on('$locationChangeStart', function (event, next, current) {
var publicPages = [''];
$rootScope.showPreloader = true;
AuthenticationService.Login(function (_data) {
if (_data.result) {
count =1;
localStorage.setItem('userInfo', angular.toJson(_data.result));
awtcDataService.employeeInfo = _data.result;
var roles = awtcDataService.employeeInfo
var route = localStorage.getItem('setRoute')
if (route == '/check') {
$state.go('help');
}
else {
$state.go('home');
}
}
if (_data.status === 401) {
$state.go('login', {reload: true})//shows the page e.g home and then redirects to this page
count =1;
event.preventDefault();
return;
$timeout(function(){
console.log("show after directive partial loaded")
});
}
else {
}
}, function (error) {
if (error.status === 401) {
$state.go('login', {reload: true})/shows the page sometimes e.g home and then redirects to this page
count =1;
event.preventDefault();
return;
$timeout(function(){
console.log("show after directive partial loaded")
});
}
});
});
All state events, (i.e. $stateChange* and friends) are now deprecated and disabled by default. Instead of listening for events, use the Transition Hook API.
https://ui-router.github.io/guide/ng1/migrate-to-1_0#state-change-events

How can I get protractor to wait for a login before proceeding?

I have a non-angular entry page to my app and I'm trying to first login:
describe('Authentication', function() {
it('should authenticate a user', function() {
browser.driver.get('https://example.com')
browser.driver.findElement(by.id('username')).sendKeys("user");
browser.driver.findElement(by.id('password')).sendKeys("mypass");
browser.driver.findElement(by.tagName('input')).click()
var url = browser.getLocationAbsUrl()
browser.driver.sleep(1)
browser.waitForAngular()
return
})
})
However, this gives an error:
Failed: Error while waiting for Protractor to sync with the page: "window.angular is undefined. This could be either because this is a non-angular page or bec
ause your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details"
What can I do to resolve this?
I wrote some helpers in the past to get this work in my e2e-tests:
waitForUrlToChangeTo: function (urlToMatch) {
var currentUrl;
return browser.getCurrentUrl().then(function storeCurrentUrl(url) {
currentUrl = url;
})
.then(function waitForUrlToChangeTo() {
browser.ignoreSynchronization = true;
return browser.wait(function waitForUrlToChangeTo() {
return browser.getCurrentUrl().then(function compareCurrentUrl(url) {
browser.ignoreSynchronization = false;
return url.indexOf(urlToMatch) !== -1;
});
});
}
);
},
login : function (username, password, url) {
browser.get('#/login');
element(by.model('username')).sendKeys(username);
element(by.model('password')).sendKeys(password);
element(by.buttonText('LOGIN')).click();
return this.waitForUrlToChangeTo(url);
}
And then in tests:
describe('when I login with valid credentials', function() {
it('should redirect to dashboard', function() {
helper.login('user', 'pass', '#/dashboard').then(function() {
expect(browser.getTitle()).toMatch('Dashboard');
});
});
});
I would say wait for logged in page until it displays properly and than do action. For e.g,
Target some element in logged-in page and wait for it.
Wait for url change, etc.
login -> browser.sleep(500)/wait for logged in page's element/URL change ->
other action
browser.driver.wait(function(){
expectedElement.isDisplayed().then(function (isVisible){
return isVisible === true;
},50000, 'Element not present ');
},50000);
if that element is not present within specified time, timeout error
would display & you would know unitl that time it's not logged in.

Can't retrieve anonymous authenticated user's info on first login

Logic: users select a few items on the homepage, then click a 'confirm' button which starts a Firebase $signInAnonymously() auth flow. Their selection is stored under their users/{uid} branch in the database tree and they are redirected to a checkout page that retrieves their selection and asks for more information to proceed.
Issue: when the user lands on the checkout page for the first time their auth state cannot be retrieved (so their selection doesn't appear). However when they refresh the page, everything works as expected for all subsequent attempts (their user info is stored and now retrievable)
Code:
User auth and selection setter/getter factory userService
var auth = $firebaseAuth();
var usersRef = $firebaseRef.users; // custom ref in app config
// Authenticate anonymously to create user session
function startSession() {
return auth.$signInAnonymously()
.then(function(user) {
return user;
})
.catch(function(error) {
console.log(error);
});
}
// Check authentication state before everything loads
function checkAuthState() {
return $q(function(resolve) {
var unsubscribe = auth.$onAuthStateChanged(function(user) {
if (user) {
unsubscribe();
resolve(user);
}
else {
console.log('User unidentified');
}
});
});
}
// Save user's menu selection into selection node of firebase
function saveSelection(items, user) {
var selectionRef = usersRef.child(user.uid).child('selection');
for (var i = 0, item; !!(item = items[i]); i++) {
var id = item.id;
if (item.selected) {
selectionRef.child(id).update(item);
}
else if (typeof(selectionRef.child(id)) !== 'undefined') {
selectionRef.child(id).remove();
}
}
}
// Get user's selection from firebase
function getSelection(user) {
var selectionRef = usersRef.child(user.uid).child('selection');
return $q(function(resolve) {
var selection = $firebaseArray(selectionRef);
resolve(selection);
});
}
Menu controller:
var menu = this;
menu.saveMenu = saveMenu;
// Save menu selection and create anonymous user on firebase
function saveMenu() {
var items = menu.items;
return userService.startSession()
.then(function(user) {
return userService.saveSelection(items, user);
});
}
Checkout controller:
// Get selected menu items from user data
function getCheckoutItems() {
return userService.checkAuthState()
.then(function(user) {
return userService.getSelection(user);
})
.then(function(selection) {
checkout.items = selection;
return checkout.items;
})
.catch(function(error) {
console.log(error);
});
}
I've looked through dozens of posts on SO before asking this. Here are a couple of the similar ones I've found:
Apparent race condition getting firebase.User from controller in Firebase 3.x
Handle asynchronous authentication in Firebase on page reload to get list that needs user's uid
I've also looked through the reference on GitHub to set it up:
https://github.com/firebase/angularfire/blob/master/docs/reference.md#firebaseauth
NB: I'm using the new version of Firebase with Angular 1.5.8:
"firebase": "^3.4.1",
"angularfire": "^2.0.2"
UPDATE
Got it. I had to add a resolve to my /checkout route to wait for authentication before loading elements on the page... Figured it out thanks to this answer by David East from the Firebase team.
resolve: {
// controller will not be loaded until $waitForSignIn resolves
"firebaseUser": function($firebaseAuthService) {
return $firebaseAuthService.$waitForSignIn();
}
}

AngularJS $location.path() not working for returnUrl

I have a SPA using AngularJS. I've just added security/authentication and everything appears to work nicely except redirecting after login if a returnUrl exists in the query string.
I have code in my app which will redirect to my login route if no user is authenticated. For example, if a user attempts to access http://localhost:55841/#/group/15 (which requires authentication), it will redirect to the login route with the following URL:
http://localhost:55841/#/login?returnUrl=%2Fgroup%2F15
Here is my login method which should redirect to the returnUrl route if it exists upon successful login:
var login = function (credentials) {
return $http.post(baseUrl + 'api/login', credentials).then(function (response) {
//do stuff
var returnUrl = $location.search().returnUrl;
if (returnUrl) {
$location.path(returnUrl);
//$location.path('/group/15');
}
$location.path('/');
});
};
When I debug the login method, the value of returnUrl is /group/15 which is what I would expect, yet it navigates to the following URL:
http://localhost:55841/#/?returnUrl=%2Fgroup%2F15
Thanks in advance
Logical code error, check this solution and your branches.
var login = function (credentials) {
return $http.post(baseUrl + 'api/login', credentials).then(function (response) {
$rootScope.currentUser = response.data;
$rootScope.$broadcast('currentUser', response.data);
var returnUrl = $location.search().returnUrl;
if (returnUrl) {
console.log('Redirect to:' + returnUrl);
$location.path(decodeURI(returnUrl)); // <- executed first, but not redirect directly.
//$location.path('/group/15');
} else { //else :)
console.log('Redirect returnUrl not found. Directing to "/".');
$location.path('/'); // <- only redirect if no returnUrl isset/true
}
}, function (response) {
$rootScope.currentUser = null;
$rootScope.$broadcast('currentUser', null);
return $q.reject(response);
});
};
Hint: You need to filter lot of URL in your "returnUrl". Think about a case where the last page was /. So its a endless loop.

Resources