I want to protect various routes on my site using Firebase V3, AngularJS and ui.router.
This looks like a similar issue. I've followed the steps from that SO post but its not working for me.
What I expect to happen:
When clicking the FAQ link I should be forwarded to the login page if logged out and should display the FAQ page when logged in.
What actually happens:
FAQ page isn't accessible at all. Logging in doesn't make any difference. It also doesn't forward me to the login page when logged out.
I'm getting this error within my run function.
ReferenceError: Firebase is not defined(…)
I've included AngularFire on the page, if I don't I get a module injector error even if I remove Firebase from the dependency array.
var app = angular.module('app', ['ui.router', 'firebase']);
app.constant('FirebaseDatabaseUrl', 'https://myfbdb.firebaseio.com');
app.config(function($stateProvider, $urlRouterProvider, $firebaseRefProvider, FirebaseDatabaseUrl) {
$firebaseRefProvider.registerUrl(FirebaseDatabaseUrl);
// If a route other than status is requested,
// go to the auth route
//$urlRouterProvider.otherwise('/logintest/login');
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'pages/login.html',
controller: 'LoginController as login'
})
.state('faq', {
url: '/faq',
templateUrl: 'pages/faq.html',
controller: 'FaqController as faq',
resolve: {
// controller will not be loaded until $requireSignIn resolves
"firebaseUser": ["$firebaseAuthService", function($firebaseAuthService) {
console.log('waitForSignIn')
// $waitForSignIn returns a promise so the resolve waits for it to complete
return $firebaseAuthService.$waitForSignIn();
}]
}
})
.state('about', {
url: '/about',
templateUrl: 'pages/about.html',
controller: 'AboutController as about',
resolve: {
// controller will not be loaded until $requireSignIn resolves
"firebaseUser": ["$firebaseAuthService", function($firebaseAuthService) {
// If the promise is rejected, it will throw a $stateChangeError
return $firebaseAuthService.$requireSignIn();
}]
}
})
});
app.controller('FaqController', ['$scope', 'firebaseUser', function($scope, firebaseUser){
console.log('faq')
}]);
app.run(["$rootScope", "$state", function($rootScope, $state) {
console.log('run');
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
// We can catch the error thrown when the $requireSignIn promise is rejected
// and redirect the user back to the home page
if (error === "AUTH_REQUIRED") {
console.log('redirecting to login page')
$state.go("login");
}
});
}]);
AngularFire versions 2.0+ are compatible with Firebase 3.0. Anything below AngularFire 2.0 is for the legacy version of Firebase.
Related
Working on my first angular app, and writing my first protractor test to check login functionality. Authentication is using ng-token-auth and I'm also using ui-router. I suspect the issue is that the test is not properly catching the redirect but I've tried various workarounds and cannot get anything to work.
Here's the basic routing and redirection code:
angular
.module('testAngularApp', [
'ngAnimate',
'ngCookies',
'ngResource',
'ui.router',
'ngSanitize',
'ng-token-auth'
])
.config(function ($stateProvider, $urlRouterProvider, $authProvider, $locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$urlRouterProvider.otherwise('/');
$authProvider.configure({
apiUrl: 'http://backend.dev'
});
$stateProvider
.state('index', {
url: "/",
templateUrl: 'views/main.html',
controller: 'MainCtrl',
controllerAs: 'main'
})
.state('app', {
url: '/app',
abstract: true,
template: '<ui-view/>',
resolve: {
auth: function($auth, $state) {
return $auth.validateUser().catch(function() {
$state.go('index');
});
}
}
})
.state('app.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
controller: 'DashboardCtrl',
controllerAs: 'dashboard'
});
})
.run(function($rootScope, $location, $state) {
$rootScope.$on('auth:login-success', function() {
$state.go('app.dashboard');
});
});
So on rendering the main page, you fill in login details, click the button, and use $state.go to head to the app dashboard. My protractor test is as follows:
describe('Budget', function() {
describe('Authentication', function() {
beforeEach(function() {
browser.get('http://localhost:9000');
});
it('should log in and redirect to the dashboard', function(done) {
element(by.model('loginForm.email')).sendKeys('someone#somewhere.com');
element(by.model('loginForm.password')).sendKeys('password');
element(by.id('login-button')).click();
expect($('h3.title').getText()).toEqual('Welcome');
});
});
});
Fairly straightforward. The error I get is:
Timed out waiting for Protractor to synchronize with the page after 11 seconds. Please see https://github.com/angular/protractor/blob/master/docs/faq.md. The following tasks were pending:
- $timeout: function () {
return _this.validateUser({
config: _this.getSavedConfig()
});
}
Notably as this test fails, it works just fine. In the chrome window that pops up, logging in works, the browser redirects and I see the Welcome text.
Things I've tried...
browser.ignoreSynchronization = true; in the test
browser.wait(...for element...) in the test
$location.path instead of $state.go
adding browser.waitForAngular() to the test
probably random other stack overflow suggestions I've found and forgotten
Any ideas?
Finally figured it out. For reference it seems to be an issue with ng-token-auth using a $timeout variable in some of it's code that confuses progractor. Fixed in this PR:
https://github.com/lynndylanhurley/ng-token-auth/pull/196
and verified fixed in the 0.29beta1 release.
I'm having the same issue using ui-router and ng-token-auth, but I finally got it resolved with a mix of those options; Try this:
it('should log in and redirect to the dashboard', function(done) {
element(by.model('loginForm.email')).sendKeys('someone#somewhere.com');
element(by.model('loginForm.password')).sendKeys('password');
element(by.id('login-button')).click();
browser.ignoreSynchronization = true;
browser.waitForAngular();
browser.sleep(5000);
$('h3.title').getText().then(function(text){
expect(text).toEqual('Welcome');
});
browser.ignoreSynchronization = false;
});
I think this could be because of a bug in ng-token-auth, but I'm not 100% sure.
edit: it was a bug in ng-token-auth, fixed in the latest version(v0.0.29-beta1)
I am using angular ui-router for routing and ng-token-auth for authentication on my website. If a signed in user tries to visit sign-in page, then he should be redirected to home page (code below).
$stateProvider
.state('sign-in',{
url: '/sign-in',
templateUrl: 'partials/registrations/sign-in.html',
controller: 'SigninCtrl as signin',
resolve: {
auth: function($auth, $state) {
$auth.validateUser().then(function(){
$state.go('home');
});
}
}
})
This works fine on state change or when I refresh the page. However, when I open the sign-in link on a new tab, it shows sign-in page for a fraction of a second and then redirects to home page.
How can I avoid showing sign-in page view before redirection?
$stateProvider
.state('sign-in',{
url: '/sign-in',
templateUrl: 'partials/registrations/sign-in.html',
controller: 'SigninCtrl as signin',
resolve: {
auth: function($auth, $state, $timeout) {
var promise = $auth.validateUser();
promise.then(function(){
$timeout(function() {
$state.go('home');
});
});
return promise;
}
}
})
You can use State Change Event - stateChangeStart .
$stateChangeStart - fired when the transition begins.
angular.module('MyApp').run(function($rootScope, $state) {
// Check authentication before changing state
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
// do authentication work here
});
});
For more help - https://github.com/angular-ui/ui-router/wiki
I am new to AngularJs. I have a single page app with routes configured having a controller and a view. The view get loaded inside the <ng-view></ng-view> element of the index.html page. Inside the controller I am making a http call to get the data and binding the data to the $scope. For success scenarios this works fine but if there is an error how do I plug in another view instead of the default view configured inside the angular route. PLease let me know.
To implement common scenario for processing ajax errors you can implement custom request interceptor and redirect user to error page (or login page) according to error status:
myApp.factory('httpErrorResponseInterceptor', ['$q', '$location',
function($q, $location) {
return {
response: function(responseData) {
return responseData;
},
responseError: function error(response) {
switch (response.status) {
case 401:
$location.path('/login');
break;
case 404:
$location.path('/404');
break;
default:
$location.path('/error');
}
return $q.reject(response);
}
};
}
]);
//Http Intercpetor to check auth failures for xhr requests
myApp.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('httpErrorResponseInterceptor');
}
]);
Plunker here
Use $location.url() to redirect to a 404.html when error is occured
$http.get(url,[params])
.success(function(data, status, headers, config){
// bind your data to scope
})
.error(function(data, status, headers, config) {
$location.url('/404');
});
Then configure your $routeProvider
$routeProvider
.when('/404', {
templateUrl: '404.html',
controller: 'Four04Controller'
})
you could use: https://github.com/angular-ui/ui-router
In case of error, you can trigger a "error" state.
I had the same problem some weeks ago and I have resolved in this way
If you use $stateProvider instead of $routeProvider you can do like this:
function routerConfig($stateProvider, $urlRouterProvider) {
$stateProvider
.state('404', {
url: '/404',
templateUrl: '404.html'
})
.state('home', {
url: '/',
templateUrl: 'home.html'
});
$urlRouterProvider.otherwise('/404');
}
Pay attention to $urlRouterProvider.otherwise(url), which is the function that gets called when the provider doesn't find the requested url, so it automatically redirect to the url provided in this function.
I am new to AngularJS. I made a simple app that have a login function using AngularJS. I used routing and on resolve i put some logic to check if user is logged in and then only proceed accordingly. I have everything working fine, the problem is, when i am not logged in, if i browse to /home it doesn't load the main.html page(that's how it's supposed to be) but a GET request gets called and that returns content of main.html in console.My code looks like this:
app.config(function($routeProvider){
$routeProvider.when('/', {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl',
resolve:{
test: function($http, $q,$location){
var defer = $q.defer();
//checks if user is logged and returns boolean
$http.post('login/getLoggedUser', {}, {}).success(function(data){
if(!data.logged){
defer.resolve(data);
$location.url('/');
}
else{
defer.resolve(data);
$location.url('/home')
}
});
return defer.promise;
}
}
})
.when('/home',{
templateUrl: 'partials/main.html',
controller: 'MainCtrl',
resolve:{
test: function($http, $q,$location){
var defer = $q.defer();
$http.post('login/getLoggedUser', {}, {}).success(function(data){
if(data.logged){
defer.resolve(data);
$location.url('/home');
}
else{
defer.resolve(data);
$location.url('/')
}
});
return defer.promise;
}
},
})
.otherwise({ redirectTo: '/' });
});
When i direct to /home, GET http:/localhost:8080/an-grails/partials/main.html is called in console which contains the content of main page. How do i disable this call? Is there any other method to do this? I read documentation on AngularJS official page and also watched few videos of Egghead.io about resolve and got idea that controller and template gets loaded only after resolve is processed, So what am i doing wrong?
The simplest way to manage rights in your different routes is to catch the $routeChangeStart which is fired by the $route service everytime the route is changed.
With this, you can access the actual route and the next one. This object is the same that you register with $routeProvider.when(). You just have to add a boolean and compare this boolean with the actual user status.
$rootScope.$on("$routeChangeStart", function(event, next, current) {
if (next.loggedOnly && !user.isLogged()) {
// You should implement a isLogged method to test if the user is logged
$location.replace();
// This prevent a redirect loop when going back in the browser
return $location.path("/");
}
}
And inside your route declaration use :
$routeProvider.when('/home', {
templateUrl: 'partials/main.html',
controller: 'MainCtrl',
loggedOnly: true
});
I have read a lot of different examples on how to implement authentication in an Angular app. I am using Firebase Simple Login as a (No-)Backend.
Users should only see any content of my app, once they are logged in. If they are logged out, all they can see is /auth/login and /auth/register. Currently, I have the following solution which works. But:
1) I am not sure whether this is bullet-proof because...
2) ... when I open /home manually while NOT logged in, I correctly get redirected to /auth/login but the HomeCtrl gets executed anyway. No sensitive data is exposed because no data is returned from the Backend, but there must be a way to NOT execute the controller at all if I am logged out?
3) Everytime I expose sensitive data, do I have to check inside my controller if the user is authenticated over and over again?
Bonus question: How do I redirect to /home after successful login? Currently, inside my AuthService I do $location.path('home'); on successful login, but this doesn't account for the state?!
My app.js:
angular.module('myApp', ['ionic', 'firebase', 'myApp.services', 'myApp.controllers'])
.run(function($rootScope, $location, AuthService) {
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
/**
* AuthService.isLoggedIn() returns TRUE or FALSE
* depending on whether user authenticated successfully
* against the Firebase backend
*/
// redirect to login
if (!AuthService.isLoggedIn() && to.name !== 'auth.register') {
$location.path('/auth/login');
}
});
})
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('auth', {
url: "/auth",
abstract: true,
templateUrl: "templates/auth-tabs.html",
controller: 'AuthCtrl'
})
.state('auth.login', {
url: '/login',
views: {
'login-tab': {
templateUrl: 'templates/auth-login.html'
}
}
})
.state('auth.register', {
url: '/register',
views: {
'register-tab': {
templateUrl: 'templates/auth-register.html'
}
}
})
.state('home', {
url: '/home',
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
});
$urlRouterProvider.otherwise('/home');
});
The way I've implemented it is by handling a 401 http server response, because I don't want to worry about checking for the user authentication state, I prefer letting the server handle that. That being said.
Documentation on $urlRouter.sync() specifies that if you prevent the default event, you may manually trigger the succesChange
angular.module('app', ['ui.router']);
.run(function($rootScope, $urlRouter) {
$rootScope.$on('$locationChangeSuccess', function(evt) {
// Halt state change from even starting
evt.preventDefault();
// Perform custom logic
var meetsRequirement = ...
// Continue with the update and state transition if logic allows
if (meetsRequirement) $urlRouter.sync();
});
});
$urlRouter documentation