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 !
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 want to find the ID of the logged in user and display it in a page. I am new to angular and I don't have much clue on how to handle a session..
I have an angular app which is connected to backend API (.net core).
I will show the instances where $rootScope is used in the website (login and authorization is already enabled). I need to get an understanding of this to learn the app.
In App.js :
//Run phase
myApp.run(function($rootScope, $state) {
$rootScope.$state = $state; //Get state info in view
//Should below code be using rootScope or localStorage.. Check which one is better and why.
if (window.sessionStorage["userInfo"]) {
$rootScope.userInfo = JSON.parse(window.sessionStorage["userInfo"]);
}
//Check session and redirect to specific page
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
if(toState && toState.data && toState.data.auth && !window.sessionStorage["userInfo"]){
event.preventDefault();
window.location.href = "#login";
}
if(!toState && !toState.data && !toState.data.auth && window.sessionStorage["userInfo"]){
event.preventDefault();
window.location.href = "#dashboard";
}
});
});
Users.js :
'use strict';
angular.module('users', []);
//Routers
myApp.config(function($stateProvider) {
//Login
$stateProvider.state('login', {
url: "/login",
templateUrl: 'partials/users/login.html',
controller: 'loginController'
});
//Factories
myApp.factory('userServices', ['$http', function($http) {
var factoryDefinitions = {
login: function (loginReq) {
$http.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
return $http.post('http://localhost:1783/api/token?UserName='+loginReq.username+'&password='+loginReq.password).success(function (data) { return data; });
}
}
return factoryDefinitions;
}
]);
//Controllers
myApp.controller('loginController', ['$scope', 'userServices', '$location', '$rootScope', function($scope, userServices, $location, $rootScope) {
$scope.doLogin = function() {
if ($scope.loginForm.$valid) {
userServices.login($scope.login).then(function(result){
$scope.data = result;
if (!result.error) {
window.sessionStorage["userInfo"] = JSON.stringify(result.data);
$rootScope.userInfo = JSON.parse(window.sessionStorage["userInfo"]);
//$localStorage.currentUser = { username: login.username, token: result.data };
//$http.defaults.headers.common.Authorization = 'Token ' + response.token;
$location.path("/dashboard");
}
});
}
};
}]);
I came to know that the information about the user will be available in $rootScope.userInfo. If so, how can I take a value inside it?
Please explain with an example if possible. Thanks in advance.
One:
myApp.controller('loginController', [
'$scope', 'userServices', '$location',
'$rootScope',
function($scope, userServices, $location, $rootScope) {
Inside the controller, $rootScope was injected which makes you have access to the userInfo in that controller.
so if you inject $rootScope into another controller and console.log($rootScope.userInfo) you would see the users data.
myApp.controller('anotherController', ['$scope', '$rootScope', function
($scope, $rootScope){
console.log($rootScope.userInfo) //you'd see the users data from sessionStorage
});
According to this post on quora
$scope is an object that is accessible from current component
e.g Controller, Service only. $rootScope refers to an object
which is accessible from everywhere of the application.
You can think $rootScope as global variable and $scope as local variables.
$rootScope Defn.
In your case, once the user is logged in a key "userInfo" in sessionStorage is created and the same data is copied to $rootScope.userInfo. To check the fields in the userInfo after login try
console.log($rootScope.userInfo);
and print it in the console or open your session storage in your browser debugger tools [for chrome open developer tools>applications>sessionstorage>domainname] to view the values in the "userInfo" key.
Suppose you have
{
uid: "10",
fullname: "John Doe"
}
you can access uid in the script using $rootScope.userInfo.uid or $rootScope.userInfo['uid'].
Just in case you are unable to read the code, here is an explanation
if (window.sessionStorage["userInfo"]) {
$rootScope.userInfo = JSON.parse(window.sessionStorage["userInfo"]);
}
is checking the user is logged in or not.
the factory
myApp.factory('userServices', ['$http', function($http) {
var factoryDefinitions = {
login: function (loginReq) {
$http.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
return $http.post('http://localhost:1783/api/token?UserName='+loginReq.username+'&password='+loginReq.password).success(function (data) { return data; });
}
}
is calling the server to get the userInfo object.
$scope.doLogin = function() {
if ($scope.loginForm.$valid) {
userServices.login($scope.login).then(function(result){
$scope.data = result;
if (!result.error) {
window.sessionStorage["userInfo"] = JSON.stringify(result.data);
$rootScope.userInfo = JSON.parse(window.sessionStorage["userInfo"]);
//$localStorage.currentUser = { username: login.username, token: result.data };
//$http.defaults.headers.common.Authorization = 'Token ' + response.token;
$location.path("/dashboard");
}
});
}
};
$scope.doLogin is calling the above factory and storing the userInfo object.
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 have multi steps form in my application and i would like to save it before go to the next step...
I did an simple example, but i'm not sure if it's the right approach.
I used resolve config and a service to access to the formData.
Config
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('home', {
url : '/',
controller : 'appCtrl',
templateUrl : 'partials/home.html',
resolve : {
saveform : function($state, FormData){
}
},
})
.state('home.state1', {
url: "state1",
templateUrl : 'partials/item1.html',
resolve : {
saveform : function($state, FormData){
return FormData.save();
}
},
})
.state('home.state2', {
url: "state2",
templateUrl : 'partials/item2.html',
resolve : {
saveform : function($state, FormData){
return FormData.save();
}
}
})
});
Controller
app.controller('appCtrl', function($scope, $rootScope, $state, saveform, FormData){
$scope.formData = {};
$scope.tempData = {};
$rootScope.$on('$stateChangeStart', function(event){
console.log($scope.tempData , $scope.formData)
if(!_.isEqual($scope.tempData , $scope.formData)){
console.log('OK CHANGE DETECTED, SAVE')
var temp = {}
FormData.set(angular.copy($scope.formData));
}else{
console.log('NO CHANGE')
}
});
$rootScope.$on('$stateChangeSuccess', function(event){
var temp = FormData.get();
if(Object.keys(temp).length!=0){
FormData.cancel();
angular.copy(temp, $scope.tempData );
angular.copy(temp, $scope.formData );
}
});
})
Service
app.service('FormData', function($q, $timeout){
this.formdata = {};
this.save = function(){
if(Object.keys(this.formdata).length===0)
return false;
var deferred = $q.defer();
$timeout(function() {
this.formdata.test = "YEAH TEST";
deferred.resolve(this.formdata);
//this.formdata = {};
}.bind(this), 1000);
return deferred.promise;
}
this.cancel = function(){
this.formdata = {};
}
this.set = function(data){
this.formdata = data;
}
this.get = function(){
return this.formdata;
}
})
Example on codepen : http://codepen.io/anon/pen/RWpZGj
I would approach this differently.
You're using multiple states for one single form. Usually you only want to save complete form data. Therefore I would devide the form into multiple sections that you can show/hide. That way, upon clicking your "next" button you can validate each step separately and show the next step once the current one is valid.
Once you've reached the last step of your form, you can save the whole thing in one go.
If you insist on using separate states:
You're using the state change to trigger a save action. Instead, use the response of your FormData service to trigger a statechange. That way you can validate and save your data before you move on to the next step. It also prevents you from saving data when someone moves to another state that is not in your multi-step form.
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.