I'm trying to redirect my user to Dashboard after Third Party Login. But when success callback is fired, the application still on Login Page, nothing happened. If I refresh the browser my interceptor catch already login and change to Dashboard... My Login Controller looks like this:
ThirdParty.login(function(result){
callbackSUCCESS(result);
},function(){});
function callbackSUCCESS(result){
AuthenticationService.login(result).then(
callbackServerSUCCESS(), function(reject) {
callbackServerERROR(reject);
});
}
function callbackServerSUCCESS() {
$scope.$apply(function() {
$state.go('dashboard');
});
}
My route in app.js
$stateProvider
.state('dashboard', {
url: '/dashboard',
views: {
'': {
templateUrl: 'views/dashboard/dashboard.html',
controller: 'DashboardCtrl'
}
}
});
My Header Controller
.controller('HeaderCtrl', ['$scope', 'AuthenticationService', '$state',
function($scope, AuthenticationService, $state) {
$scope.logout = function() {
AuthenticationService.logout().then(callbackServer(), callbackServer());
};
function callbackServer() {
$state.go('login');
}
}
]);
Authentication Controller Angular Factory
var headersConfig = {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
};
return {
login: function(credentials) {
var deferred = $q.defer();
$http.post('/api/users/sign_in', sanitizeCredentials(credentials), {
headers: headersConfig,
timeout: deferred.promise
}).then(function(result) {
if (result.status === 200) {
UserSessionService.cacheSession(result.data);
deferred.resolve();
} else {
deferred.reject();
}
}, function(reject) {
UserSessionService.clean();
deferred.reject(reject);
});
$timeout(function() {
deferred.resolve();
}, 15000);
return deferred.promise;
}
};
I can't remember the exact semantics of $state.go, but usually you need to use $scope.$apply in some manner when responding to events Angular isn't aware of, to ensure a digest cycle occurs. I.e. you could try:
ThirdParty.login(function(result) {
$scope.$apply(function() {
$state.go('dashboard');
});
}, function() {});
You need to get a reference to a scope from somewhere, but one shouldn't be hard to find in an Angular app.
Get rid of both () in
then(callbackServer(), callbackServer())
Related
I have a login system...and I'm trying to implement login/logout feature to a website.
This is the fiddle I'm following- FIDDLE
This is my route file:
(function() {
var onlyLoggedIn = function($location, $q, AuthService2) {
var deferred = $q.defer();
if (AuthService2.isLogin()) {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
return deferred.promise;
};
angular.module('myApp', [
'ngRoute',
'myApp.login',
'myApp.home',
'myApp.logout',
'myApp.notifications'
])
.factory('AuthService2', ["$http", "$location", function($http, $location) {
//var vm = this;
var baseUrl = 'api/';
return {
isLogin: function() {
var token;
if (localStorage['entrp_token']) {
token = JSON.parse(localStorage['entrp_token']);
} else {
token = "";
}
var data = {
token: token
};
$http.post(baseUrl + 'validateUserToken', data).success(function(response) {
if (response.msg == "authorized") {
//console.log(response.msg);
return localStorage.isLogged === "true";
} else {
return localStorage.isLogged === "false";
}
});
}
}
return {
isLogin: isLogin
};
}])
.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/login', {
controller: 'LoginController',
templateUrl: 'app/components/login/loginView.html',
controllerAs: 'vm'
})
.when('/home', {
controller: 'HomeController',
templateUrl: 'app/components/home/homeView.html',
resolve: {
loggedIn: onlyLoggedIn
},
controllerAs: 'vm'
})
.when('/logout', {
controller: 'LogoutController',
templateUrl: 'app/components/login/loginView.html',
resolve: {
loggedIn: onlyLoggedIn
},
controllerAs: 'vm'
})
.when('/notifications', {
controller: 'NotificationsController',
templateUrl: 'app/components/notifications/notificationsView.html',
resolve: {
loggedIn: onlyLoggedIn
},
controllerAs: 'vm'
})
.otherwise({
redirectTo: '/login'
});
}]);
})();
Users will be visiting login page. After authentication, I set a session and a local storage token.
I use resolve to check whether the user is valid or not. I defined a function for that (as you can see it from the code).
Upon login, I'm able to validate the user and set the session and login controller redirects the valid user to home. But the user reaches the route and he stays in login page itself.
My http request is fine. I checked it and it returns correct result. But I think the function returns false and afterthat only the http request is getting executed. because I can see the http request in consoel and it returns positive result, so the user should get navigated but it's not happening because http request is getting delayed or something.
I tried giving an alert message inside this if else,and it always goes to else condition, no matter what.
if (AuthService2.isLogin()) {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
Why is it so? I'm pretty much new to ANgular. Is this a limitation of angular?
The problem is that there is no return statement in your isLogin function.
Your return statement is inside the $http.post callback NOT isLogin hence isLogin return undefined thus resolved to falsy.
$http.post(baseUrl + 'validateUserToken', data).success(function(response) {
//Notice that you are inside function(response) closure.
if (response.msg == "authorized") {
return localStorage.isLogged === "true";
} else {
return localStorage.isLogged === "false";
}
});
I can suggest you to return $http.post instead, $http.post return a promise object which you can use in such way, below is an brief example on how you can do it.
isLogin: function() {
//Omitted some code
//This method return a promise object
return $http.post(baseUrl + 'validateUserToken', data);
});
AuthService2.isLogin().then(function(res){
if (response.msg == "authorized") {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
}, errorCallback); //Optional if you want to handle when post request failed
Code is not tested, just to give you the idea.
I am using Facebook SDK to build an application however I am faced with a challenge, I try to get the login status of the user before I redirect him to his profile page, however during the call to get the login status I get the error that
ReferenceError: FB is not defined
now the SDK is being loaded asynchronously so the error makes sense, how can i resolve the problem. Here is my code:
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: "Views/ListPages.html",
controller: 'MainCtrl',
resolve: {
authentication:["$location", "LoginFactory", function($location, LoginFactory){
console.log("in resolve");
LoginFactory.getLoginStatus()
.then(function(response){
if(response){
$location.path('/login');
}
else{
$location.path('/');
}
});
}]
}
})
.when('/login', {
templateUrl: "Views/Login.html",
controller: 'LoginCtrl'
})
.otherwise
({redirectTo: '/'});
});
loginApp.factory("LoginFactory", function ($rootScope, $q, $location, UserInfo) {
return {
getLoginStatus: function () {
var deferred = $q.defer();
FB.getLoginStatus(function (response) {
if(!response || response.error){
deferred.reject(new error("User Not logged in."));
}
else{
deferred.resolve(response);
}
});
return deferred.promise;
},
login: function () {
FB.login(function (response) {
if (response.authResponse === "connected") {
$rootScope.$broadcast('fb_connected', {facebook_id:response.authResponse.userID});
} else {
$rootScope.$broadcast('fb_login_failed');
}
}, {scope: "read_insights, publish_pages, manage_pages"});
},
logout: function () {
FB.logout(function (response) {
if (response) {
$rootScope.$broadcast('fb_logout_succeded');
$location.path('/login');
} else {
$rootScope.$broadcast('fb_logout_failed');
}
});
}
};
angular.module("LoginCtrlModule", ["FacebookLogin"])
.controller("LoginCtrl", ["$scope", "$location", "LoginFactory", function ($scope, $location, LoginFactory) {
$scope.login = function () {
LoginFactory.login();
$scope.on("fb_connected", function () {
$location.path("/");
});
$scope.on("fb_login_failed", function(){
$location.path("/login");
});
}
}]);
app.run(function ($rootScope, $location, LoginFactory) {
window.fbAsyncInit = function () {
FB.init({
appId: '',
status: true,
cookie: true,
xfbml: true
});
};
(function (d) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement('script');
js.id = id;
js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
});
I was following these posts:
AngularJS- Login and Authentication in each route and controller
http://www.sitepoint.com/implementing-authentication-angular-applications/
But the problem i am facing is different.
Thanks for the help.
I think the solution is to not call the FB.getLoginStatus() but rather have an object that contains the User information and the access token, each time I try to route I should check if the User info object is null, in case if its not null then call the check login status method and accordingly take the route or not to take the route.
I'm using Angular with Firebase and UI Router. I'm using anonymous authentication. When a session expires, I would like the unauthenticated user to be redirected to the homepage. I've used the Yeoman Angularfire Generator as a model. But when I use the code below, an authenticated page does not redirect when a user is already on that page and the session expires.
.config(['$urlRouterProvider', 'SECURED_ROUTES', function($urlRouterProvider, SECURED_ROUTES) {
$urlRouterProvider.whenAuthenticated = function(path, route) {
route.resolve = route.resolve || {};
route.resolve.user = ['Auth', function(Auth) {
return Auth.$requireAuth();
}];
$urlRouterProvider.when(path, route);
SECURED_ROUTES[path] = true;
return $urlRouterProvider;
};
}])
.run(['$rootScope', '$location', 'Auth', 'SECURED_ROUTES', '$state',
function($rootScope, $location, Auth, SECURED_ROUTES, $state) {
Auth.$onAuth(check);
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
if (error === 'AUTH_REQUIRED') {
$state.go('home');
}
else {
console.log('authenticated');
}
});
function check(user) {
if (!user && authRequired($location.path())) {
$state.go('home');
}
}
function authRequired(path) {
return SECURED_ROUTES.hasOwnProperty(path);
}
}
])
.constant('SECURED_ROUTES', {});
Router
.state('selection', {
url: '/selection',
authRequired: true,
views: {
'list': {
templateUrl: 'app/views/project-list.html',
controller: 'ProjectListCtrl as projectList',
resolve: {
'user': ['Auth', function(Auth) {
return Auth.$waitForAuth();
}]
}
},
'selectionlist': {
templateUrl: 'app/views/selection-list.html',
controller: 'SelectionListCtrl as selectionList'
resolve: {
'user': ['Auth', function(Auth) {
return Auth.$waitForAuth();
}]
}
}
}
})
This is a common problem in Single Page Applications. With Angular you can solve it with $http interceptor.
The idea is if the session is expired the first time when the user needs some action from the backend to be redirected to the login page.
Expired session is detected from the response itself.
Example:
.factory('httpAuthInterceptor', function ($q) {
return {
'responseError': function (response) {
// NOTE: detect error because of unauthenticated user
if ([401, 403].indexOf(response.status) >= 0) {
// redirecting to login page
$state.go('home');
return response;
} else {
return $q.reject(rejection);
}
}
};
})
.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpAuthInterceptor');
});
Have you read through this?
https://www.firebase.com/docs/web/libraries/angular/guide/user-auth.html
I'd read the "Authenticating with Routers" section.
Edit
In that case you can grab the 'expires' value from Auth.$getAuth() and use it in a $timeout on $routeChangeSuccess. Essentially creating a countdown to session timeout after every route change...like so:
$rootScope.$on("$routeChangeSuccess", function() {
var sessionTimeout = Auth.$getAuth().expires * 1000;
var d = new Date();
var n = d.getTime();
var timer = sessionTimeout - n;
console.log(timer);
$timeout(function() {
$location.path("/");
},timer);
});
As you said "when a user is already on that page and the session expires.", that case you can use
$location.path('/home')
And for auto detect idle session expiration Ng-Idle, see the demo
I was able to get the redirect working with UI Router by modifying the following code from my question above:
From this (not working):
function check(user) {
if (!user && authRequired($location.path())) {
$state.go('home');
}
}
To this (working):
function check(user) {
if(!user && $state.current.authRequired===true) {
$state.go('home');
}
else {console.log('logged in')}
}
You can make use of ajaxSetup in order to catch 301 errors, something like this:
$.ajaxSetup({
error : function(jqXHR, textStatus, errorThrown) {
if (jqXHR.status == 0) {
alert("Element not found.");
} else {
alert("Error: " + textStatus + ": " + errorThrown);
}
}
});
I am sure this is a configuration error, but not sure how to get round it correctly with angular.
I have my states setup like so
$stateProvider
.state('app', {
url: '',
abstract: true,
templateProvider: ['$templateCache', function ($templateCache) {
return $templateCache.get('app/main/main.html');
}],
controller: 'MainController',
resolve: {
Pages: ['PageFactory', function (PageFactory) {
return PageFactory.getAll();
}]
}
})
.state('app.home', {
url: '/home',
views: {
'content#app': {
templateProvider: ['$templateCache', function ($templateCache) {
return $templateCache.get('app/page/page.html');
}],
controller: 'PageController'
}
},
resolve: {
Page: ['$stateParams', 'PageFactory', function ($stateParams, PageFactory) {
return PageFactory.get($stateParams.id);
}],
ModuleData: function () {
return {};
},
Form: function () {
return {};
}
}
});
Now my problem is that if PageFactory.get($stateParams.id) fails, then the page is reloaded, and reloaded and reloaded.
Here is a sample from PageFactory, which as you can see returns a promise
angular.module('app').factory('PageFactory', ['$http', '$q', function ($http, $q) {
var urlBase = 'api/page';
var factory = {};
factory.getAll = function () {
var $defer = $q.defer();
$http.get(urlBase)
.success(function (data) {
$defer.resolve(data);
})
.error(function (error) {
$defer.reject(error);
});
return $defer.promise;
};
factory.get = function (id) {
var $defer = $q.defer();
$http.get(urlBase + '/' + id)
.success(function (data) {
$defer.resolve(data);
})
.error(function (error) {
$defer.reject(error);
});
return $defer.promise;
};
}]);
It is possible to restrict the number of times the resolve is attempted, or should i have set this up differently in the first place?
I noticed this when moving the site from 1 place to another and needed to specify a base href in my index.html page. Because the $http was trying to connect to a url that didnt exist, it just kept on trying and trying and trying, which if someone left it would harn our web servers performance.
My state app is my route state (abstract state) and the user defaults to app.home uponing entering the site. I guess this is why it just keeps retrying the resolve?
We also had infinite loops when there were errors in resolves, so we ended up having more logic in the $stateChangeError event handler. Since we have this, we had no more troubles with loops.
See how we check where we were trying to go to and if we failed while going to a home state, we do not try again and redirect to a simple error state instead.
Here our example, this is setup in our main module's run method:
$rootScope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
console.log('Error on StateChange from: "' + (fromState && fromState.name) + '" to: "'+ toState.name + '", err:' + error.message + ", code: " + error.status);
if(error.status === 401) { // Unauthorized
$state.go('signin.content');
} else if (error.status === 503) {
// the backend is down for maintenance, we stay on the page
// a message is shown to the user automatically by the error interceptor
event.preventDefault();
} else {
$rootScope.$emit('clientmsg:error', error);
console.log('Stack: ' + error.stack);
// check if we tried to go to a home state, then we cannot redirect again to the same
// homestate, because that would lead to a loop
if (toState.name === 'home') {
return $state.go('error');
} else {
return $state.go('home');
}
}
});
The common way to use resolve with promises is to use $q.defer()
Pages: ['PageFactory', '$q', function (PageFactory, $q) {
var deffered = $q.defer();
PageFactory.getAll()
.success(function(data) {
deffered.resolve(data);
})
.error(function(data) {
deffered.reject();
})
return deferred.promise;
}]
This will reject the the state change if it fails, or you can do whatever when it fails. And it passes the data through if it succeeds.
Also see this post about the same thing.
Angular ui-router get asynchronous data with resolve
My goal is cancel a xhr request if
the lastest has a 401 status.
With the code below I can cancel
the next request but if I try
to do
an other request (ie do login) is aborted
(it need like canceller.reject(reason))
The flow
1 set a parent route (auth) to check if the login is required
2 set a child root which inheriting the auth state
3 when the app hits the route cancel the request to articles and show a modal login
(function() {
'use strict';
function Auth($http) {
return {
isLoggedIn: function() {
return $http.get('/api/auth/isloggedin');
},
signin: function(data) {
return $http.post('/api/auth/signin', data);
}
};
}
function loginModal($modal, $rootScope,$templateCache) {
function successCallback (data) {
console.log('success',data);
}
function errorCallback (data) {
console.log('fail',data);
}
return {
open : function(){
var modalInstance = $modal.open({
template: $templateCache.get('auth/templates/modal-login.html'),
controller: 'LoginModalController',
controllerAs: 'auth'
});
return modalInstance.result.then(successCallback).catch(errorCallback);
}
};
}
function HttpInterceptor($rootScope,$q) {
var canceller = $q.defer();
return {
'request': function(config) {
config.requestTimestamp = new Date().getTime();
config.timeout = canceller.promise;
return config;
},
'response': function(response) {
response.config.responseTimestamp = new Date().getTime();
return response;
},
'responseError': function(rejection) {
if (rejection.status === 401) {
$rootScope.$emit('no-auth', rejection);
canceller.resolve('Unauthorized');
}
return $q.reject(rejection);
}
};
}
angular.module('auth.services', [])
.factory('Auth', Auth)
.factory('HttpInterceptor', HttpInterceptor)
.factory('loginModal', loginModal);
})();
(function() {
'use strict';
angular.module('auth',
['auth.services','auth.controllers','auth.routes'])
.run(function($rootScope,loginModal,HAS_MODAL_LOGIN){
$rootScope.$on('no-auth', function(event, data) {
if(HAS_MODAL_LOGIN){
loginModal.open();
}
});
});
})();
(function() {
'use strict';
function config($stateProvider,$httpProvider) {
$stateProvider
.state('auth', {
abstract: true,
template: '<ui-view/>',
resolve:{
auth : function(Auth){
return Auth.isLoggedIn();
}
}
});
//Http Intercpetor to check auth failures for xhr requests
$httpProvider.interceptors.push('HttpInterceptor');
}
angular.module('auth.routes', [])
.config(config);
})();
(function() {
'use strict';
function config($stateProvider) {
$stateProvider
.state('auth.articles', {
url: '/articles',
templateUrl: 'articles/templates/index.html',
controller:'ArticlesController as articles',
resolve:{
articlesData : function(Articles){
return Articles.get();
}
}
});
}
angular.module('articles.routes', [])
.config(config);
})();
You do not need to reject. If you have 401 error, then you may simply open login dialog right there. And if user closed dialog, navigate to home.
Please read this article by Gabe Scholz
http://brewhouse.io/blog/2014/12/09/authentication-made-simple-in-single-page-angularjs-applications.html
This is exactly what you try to do but much cleaner way.