I have a angularjs ui-router situation where:
User must be authorized before hitting any page
If user is authorized and has no route, redirect to their homepage
If user is authorized and has a route, redirect to route
If user is authorized and has no route and no homepage, navigate to default page
If user is not authorized and has route, redirect to login page and upon authorization redirect to that route
Its a tricky situation and I can't seem to nail it just right. My current code does work but... it has to shows the 'login' page for a split second before navigating. This happens because I have to kick off the $stateChangeStart somehow.
var app = angular.module('myapp', ['ui.router']);
// handle default states based on authentication,
// default properties set in user profile, or
// or just redirect to 'apps' page
var authd = false,
defaultDashboard = undefined,
defaultFn = function($injector){
// required to get location since loaded before app
var $location = $injector.get('$location');
// if the user has a default dashboard, navigate to that
if(defaultDashboard){
$location.path('workspace/' + defaultDashboard);
} else if(authd) {
// if the user is auth'd but doesn't have url
$location.path('home');
} else {
// if we aren't auth'd yet
$location.path('login');
}
};
app.config(function ($urlRouterProvider, $locationProvider, $stateProvider) {
$locationProvider.html5Mode(true);
app.stateProvider = $stateProvider;
$urlRouterProvider.otherwise(function($injector){
defaultFn($injector);
});
});
app.run(function ($rootScope, $q, $location, $state, $stateParams, $injector, security) {
var deregister = $rootScope.$on("$stateChangeStart", function () {
// authorize is a AJAX request to pass session token and return profile for user
security.authorize().success(function(d){
// set some local flags for defaultFn
authd = true;
defaultDashboard = d.defaultDashboard;
// de-register the start event after login to prevent further calls
deregister();
// switch to default view after login
if($location.$$url === "/login" ||
$location.$$url === "/"){
defaultFn($injector);
}
}).error(function(){
$location.path('login');
});
});
});
I'm using a inceptor to handle 401s like:
var module = angular.module('security.interceptor', []);
// This http interceptor listens for authentication failures
module.factory('securityInterceptor', function($injector, $location) {
return function(promise) {
// Intercept failed requests
return promise.then(null, function(originalResponse) {
if(originalResponse.status === 401) {
$location.path('/login');
}
return promise;
});
};
});
// We have to add the interceptor to the queue as a string because the
// interceptor depends upon service instances that are not available in the config block.
module.config(function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
$httpProvider.responseInterceptors.push('securityInterceptor');
});
anyone had any similar cases and found a better solution?
Heres my solution I came up with:
app.config(function ($urlRouterProvider, $locationProvider, $stateProvider) {
$locationProvider.html5Mode(true);
// placeholder
$stateProvider.state('welcome', {
url: '/'
});
$urlRouterProvider.otherwise('404');
});
app.run(function ($rootScope, $q, $location, $state, $stateParams, security, $urlRouter) {
var deregister = $rootScope.$on("$stateChangeStart", function (event) {
// stop the change!
event.preventDefault();
security.authorize().success(function(d){
// if we don't have a previous url
if($location.$$url === "/" || $location.$$url === "/login"){
// If user has a preset home
if(d.defaultDashboard){
$location.path('workspace/' + d.defaultDashboard);
} else {
$location.path('welcome');
}
} else {
// if we do, then continue
$urlRouter.sync();
}
}).error(function(){
// redirect to home
$location.path('login');
});
// deregister the listener
deregister();
});
});
Essentially, creating a empty route for an empty route solved my problem. Interesting.
Related
I am using ui-router. I am trying to authenticate all pages except sign up page.
Here are important parts of code:
In app.js:
$transition.onStart({ to: function(state) {
return state.data != null && state.data.authRequired === true;
}},function(trans){
var AuthService = trans.injector().get('AuthService');
....
});
In routes.js:
$stateProvider.state('signup', {
url: '/signup',
templateUrl:'views/signeup.html',
controller: 'SigneUp',
data: {
authRequired: false
}
});
But I am not allowed to go to signup page unless I am authenticated.
You will need to have a service that does Authorization and stores state of current auth for any given user. From there, in each of your controllers, check for auth status, where required, allow access when not logged in; where not, make a stop gate.
eg:
angular.module('app', [])
.controller('ctrlname', ['$scope', '$location', 'myAuthenticationService', function($scope, $location, myAuthenticationService){
//userId and Password to be bound to partials via ng-model.
if (myAuthenticationService.authorizeUser(userId, password)){
// DO what you have to for an authorized user.
}
else{
//
$location.route('/unauthorized');
}
}]
.service('myAuthenticationService', ['$http', function($http){
var self = this;
//This is just for reference, might need more sophesticated authentication on server side anyways.
self.authorizeUser = function(userId, password){
return $http.get(`url/?userId=${userId}&password=${password}`)
.success(function(response){
//If user is validated,
return true;
})
.error(function(error){
return false;
})
}
return {
authorizeUser: function(userId, password){
return self.authorizeUser(userId, password);
}
}
}]
You could define your routes and add corresponsing controllers in routes.js.
I'm using Satellizer for authentication in my Angular app and have pretty much everything working... except that I can't seem to figure out how to display the username (or email) after successful login in the navbar.
My login controller looks like this
$scope.login = function() {
$auth.login($scope.user).then(function(response) {
$scope.user = JSON.stringify(response.data.user);
localStorage.setItem('user', user);
$scope.user = response.data;
$rootScope.authenticated = true;
$rootScope.currentUser = response.data.user;
// redirect user here after successful login
$state.go('home');
}
}
I have this in my $states (using UI Router) for global access
.run(function ($rootScope, $auth, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, fromState) {
if (toState.loginRequired && !$auth.isAuthenticated()) {
//if ($auth.isAuthenticated()) {
$rootScope.currentUser = JSON.parse($state, localStorage.currentUser);
//$rootScope.currentUser = JSON.stringify($state, localStorage.currentUser);
$state.go('/login');
event.preventDefault();
};
});
});
And then this in my navbar controller
.controller('NavbarCtrl', function($scope, $rootScope, $window, $auth) {
$scope.isAuthenticated = function() {
return $auth.isAuthenticated();
$scope.user.email = $localStorage.currentUser.email;
}
}
I'm not getting any errors in the console, so I'm not sure exactly where I'm going wrong...?
currentUser is undefined in the localStorage, I thought I was setting that in my login controller code above...?
First, you store the user object after login in localStorage.user, then you read currentUser in NavbarCtrl with
$localStorage.currentUser.email;
You should use the same property user, i.e
$scope.user.email = localStorage.user.email;
But then, why do you need this in local storage?
since you put the currentUser in the $rootScope, you should be able to directly use it in your navbar, e.g.
<span>{{currentUser.email}}</span>
I have 3 page A, B and C. A requires login;
user click on A
check for authentication ( whether user logged in or not)
if user not logged in than login page appears
after successful login , sent back to A
right now what I have done so far; here is my working plunker
add params to login state and set to null in .config block
$stateProvider
.state("login", {
url: '/login',
controller: 'LoginController',
data : { pageTitle : 'Home', requiresAuth: true }
params : {'returnTo' : null }
});
When user click to the authenticated page, we send user to login page with additional parameter in $state.target
$transitions.onBefore(requiresAuthCriteria, redirectToLogin, {priority: 10} );
var requiresAuthCriteria = {
to: function (state) {
return state.data && state.data.requiresAuth;
}
};
var redirectToLogin = function($transition$, $injector) {
var AuthService = $injector.get('AuthService');
if (!AuthService.isAuthenticated()) {
return $state.target('login', { returnTo : $transition$.to().name });
}
};
On login controller; after successful login ,we bind parameter to rootScope and .broadcast the event
login
.controller('LoginController', function($rootScope, AUTH_EVENTS, AuthService, $stateParams){
var self = this;
self.login = function(credentials){
AuthService.login(credentials).then(function(user){
$rootScope.returnTo = $stateParams.returnTo;
$rootScope.$broadcast(AUTH_EVENTS.loginSuccess);
}, function (){
$rootScope.$broadcast(AUTH_EVENTS.loginFailed);
});
};
On .run block we catch this broadcasted event and send user to the requested page via $state.go
$rootScope.$on(AUTH_EVENTS.loginSuccess, function(event) {
$timeout(function() { $state.go($rootScope.returnTo); });
});
BUT I think, this is not a valid approach to do so as I also need to check authentication; I tried with resolve of .state
resolve : { returnTo : returnTo }
function returnTo ($transition$) {
var redirectedFrom = $transition$.previous();
// The user was redirected to the login state (via the requiresAuth hook)
if (redirectedFrom !== null) {
// Follow the current transition's redirect chain all the way back to the original attempted transition
while (redirectedFrom.previous()) {
redirectedFrom = redirectedFrom.previous();
}
return { state: redirectedFrom.to(), params: redirectedFrom.params("to") };
}
var fromState = $transition$.from();
var fromParams = $transition$.params("from");
console.log(fromState);
if (fromState.name !== '') {
return {state: fromState, params: fromParams};
}
// If the fromState's name is empty, then this was the initial transition. Just return them to the home state
return { state: 'base' };
}
but do not understand why this is not working ; Kindly help me to fix this problem
got the solution after a lot of surfing through various articles
now the alternative is using resolve attribute; remove params attribute and it's all code
on login state, add resolve property and also add onEnter property so that user can not access /login once logged in.
.state("login", {
url: '/login',
templateUrl: './user/login.html',
controller: 'LoginController',
controllerAs : 'vm',
resolve: { returnTo: returnTo },
onEnter: function($state, AuthService){
if(AuthService.isAuthenticated()) {
return $state.target('base', undefined, { location: false });
}
}
})
define returnTo function copied from here
function returnTo ($transition$) {
var redirectedFrom = $transition$.previous();
// The user was redirected to the login state (via the requiresAuth hook)
if (redirectedFrom !== null) {
// Follow the current transition's redirect chain all the way back to the original attempted transition
while (redirectedFrom.previous()) {
redirectedFrom = redirectedFrom.previous();
}
// return to the original attempted "to state"
return { state: redirectedFrom.to(), params: redirectedFrom.params("to") };
}
// The user was not redirected to the login state; they directly activated the login state somehow.
var fromState = $transition$.from();
var fromParams = $transition$.params("from");
if (fromState.name !== '') {
return {state: fromState, params: fromParams};
}
// If the fromState's name is empty, then this was the initial transition. Just return them to the home state
return { state: 'base' };
}
The real mystery is how does this $transitions$ is available inside the function!!!, someone can explain than would be thankful
now inject $state and returnTo ( function name as it is) in .loginController and after successful logged in , change the state using state and params value which was return as object from returnTo function
login
.controller('LoginController', function($rootScope, AUTH_EVENTS, AuthService, $state, returnTo){
var self = this;
self.login = function(credentials){
AuthService.login(credentials).then(function(user){
$rootScope.$broadcast(AUTH_EVENTS.loginSuccess);
AuthService.setCurrentUser(user);
$state.go(returnTo.state, returnTo.params, {reload: true });
}, function (){
$rootScope.$broadcast(AUTH_EVENTS.loginFailed);
});
};
hope it helps someone
I'm working on an Angular web app that is using Firebase to authenticate the user when they login or register. I want to restrict going to the /dashboard url unless they're logged in. I tried following Firebase's docs, but I'm coming up with errors.
I think where I'm having problems was making my controller code work with the one provided. I kept getting the error "Unknown provider: AuthProvider <- Auth <- currentAuth", so I just took out their controller code for now.
Any help would be great!
Here's the doc link: https://www.firebase.com/docs/web/libraries/angular/guide/user-auth.html#section-routers
And my code:
ROUTER CONFIG
var app = angular.module('maggsLashes', ['ngRoute', 'ui.calendar', 'firebase']);
app.config(function($routeProvider) {
$routeProvider
.when('/dashboard', {
templateUrl: 'app/templates/dashboardTmpl.html',
controller: 'dashboardCtrl',
resolve: {
// controller will not be loaded until $requireAuth resolves
// Auth refers to our $firebaseAuth wrapper in the example above
"currentAuth": ["Auth", function(Auth) {
// $requireAuth returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError (see above)
return Auth.$requireAuth();
}]
}
})
DASHBOARD CONTROLLER
app.controller('dashboardCtrl', function($scope, $firebaseAuth, $location) {
var ref = new Firebase("https://maggslashes.firebaseio.com/");
var auth = $firebaseAuth(ref);
// app.controller("AccountCtrl", ["currentAuth", function(currentAuth) {
// // currentAuth (provided by resolve) will contain the
// // authenticated user or null if not logged in
// }]);
// console.log(auth);
console.log("Matt is pretty much awesome");
ref.onAuth(function(authData) {
if (authData) {
console.log("User is authenticated w uid:", authData);
}
else {
console.log("client sucks");
}
});
$scope.logOut = function() {
$location.path('/');
$scope.apply();
ref.unauth();
console.log(authData.uid);
};
});
I can see what may be at least on issue. In your router, you refer to "Auth" but do not see your code for that service/factory, so it's possible you don't have that set up. Note that "Auth" is a custom factory.
not you need this somewhere in your code:
app.factory("Auth", ["$firebaseAuth",
function($firebaseAuth) {
var ref = new Firebase("https://docs-sandbox.firebaseio.com", "example3");
return $firebaseAuth(ref);
}
])
Also, can you provide the errors you're receiving?
Wayne
The problem I'm having is that after a user logs-in or signs-up they are redirected to the games view, this happens within the createUser function or after a successful authentication; in either case the redirect is handled with $state.go('games').
This all works fine, however, if the user navigates away from the games view to any other state like createGame or dashBoard and then refreshes the browser in one of those views they are always redirected back to the games view. When I remove the $state.go('games') this doesn't happen, and reload will only reload the current view (like it should).
I have tried changing parameters on $state.go and tried using $state.transitionTo(), but nothing changes this behavior.
Is this just normal behavior for $state.go? If not, am I using it wrong, and are there other ways to redirect? What can I do to stop this behavior?
var game = angular.module('game', ['ui.router','firebase']);
game.config(['$stateProvider', '$locationProvider', function($stateProvider,$locationProvider) {
$locationProvider.html5Mode(true);
$stateProvider.state('signUp', {
url: '/signUp',
templateUrl: '/templates/signUp.html'
});
$stateProvider.state('games', {
url: '/games',
templateUrl: '/templates/games.html',
controller: 'games.controller'
});
$stateProvider.state('login', {
url: '/login',
templateUrl: '/templates/login.html'
});
$stateProvider.state('dashboard', {
url: '/dashboard',
templateUrl: '/templates/dashboard.html',
controller: 'dashboard.controller'
});
$stateProvider.state('createGame', {
url: '/createGame',
templateUrl: '/templates/createGame.html',
controller: 'createGame.controller'
});
}]);
// Root reference to database
game.factory("Fire", function($firebaseAuth) {
var ref = new Firebase("https://money-game.firebaseIO.com/");
return ref;
});
// Gives access to auth methods
game.factory("Auth", ["$firebaseAuth", "Fire",
function($firebaseAuth, fire) {
return $firebaseAuth(fire);
}
]);
game.controller('app.controller', ['$scope', '$state', '$stateParams', 'Auth', 'Fire', function ($scope, $state, $stateParams, auth, fire) {
$scope.user = {
email : '',
password : ''
};
$scope.signUp = function() {
auth.$createUser($scope.user)
.then(function(userData) {
// After successful signup save a user record to users under their auth ID
fire.child('users').child(userData.uid).set({
name : $scope.user.name,
email : $scope.user.email,
joined : Date.now()
});
$state.go('games');
console.log("User " + userData.uid + " created successfully!");
})
.catch(function(error) {
console.error("Error: ", error);
});
};
$scope.login = function() {
auth.$authWithPassword($scope.user).catch(function(error) {
console.error("Authentication failed:", error);
});
};
$scope.logout = function() {
auth.$unauth();
window.location = '/';
};
auth.$onAuth(function(authData) {
if (authData) {
$scope.activeUser = authData;
// After user logs in find user record by auth id
fire.child('users').child(authData.uid).on('value', function(snapshot) {
// Checks if user exsists
if (snapshot.exists()) {
// sets scope user to the user data
$scope.user = snapshot.val();
// sets scope user id to the auth id
$scope.user.id = snapshot.key();
}
});
console.log("Logged in as:", authData.uid);
$state.go('games');
} else {
$scope.activeUser = false;
// $scope.user = '';
}
});
}]);
game.controller('games.controller', ['$scope', '$state', '$stateParams', 'Auth', '$firebaseArray','Fire', function ($scope, $state, $stateParams, auth, $firebaseArray, fire) {
$scope.games = $firebaseArray(fire.child('games'));
$scope.view = 'listView';
$scope.setCurrentGame = function(game) {
$scope.currentGame = game;
};
$scope.addPlayer = function(game) {
console.log(game.$id);
var ref = fire.child('players').child(game.$id);
ref.push({
id : $scope.user.id,
name : $scope.user.name,
email : $scope.user.email
})
};
// swap DOM structure in games state
$scope.changeView = function(view){
$scope.view = view;
}
}]);
game.controller('createGame.controller', ['$scope', '$state', '$stateParams', 'Auth', '$firebaseArray','Fire', function ($scope, $state, $stateParams, auth, $firebaseArray, fire) {
$scope.games = $firebaseArray(fire.child('games'));
$scope.createGame = function() {
if ($scope.format == 'Match Play') {
$scope.skinAmount = 'DOES NOT APPLY';
$scope.birdieAmount = 'DOES NOT APPLY';
}
$scope.games.$add({
name: $scope.gameName,
host: $scope.user.name,
date: $scope.gameDate,
location: {
course: $scope.courseName,
address: $scope.courseAddress
},
rules: {
amount: $scope.gameAmount,
perSkin: $scope.skinAmount,
perBirdie: $scope.birdieAmount,
format: $scope.format,
holes : $scope.holes,
time: $scope.time
}
})
// $state.go('games');
};
}]);
It's not about $state.go
It's actually about when you call it, $state.go does one simple thing : trigger a change in the routing state off your application. You just happen to do it everytime your application authenticates your user against your $firebaseAuth service, in the $onAuth handler.
A single page app is an app
When you refresh the page in the browser, the entire app reloads, starts again, boostraps everything to display your app. This startup process includes re-authenticating your user (I didn't quite see where it is done in your code, but it has to be done), thus triggering the $onAuth handler again… and ultimately execute $state.go('games') again.
Put your $state.go invocation elsewhere
You don't actually mean to do it every time your app authenticates the user, you rather want to do it when your user performs a successful login or sign-up action.
$authWithPassword returns a promise, you could do the state change in a success callback when the promise is resolved.
Hope that helps !