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
});
Related
Im building an app in angularJS where a user can Login to the the admin Panel.
Router config
app.config(["$routeProvider","$locationProvider",($routeProvider,$locationProvider)=>{
$locationProvider.hashPrefix("");
$routeProvider
.when("/",{
templateUrl: "app/views/enterticket.html"
})
.when("/adminlogin",{
templateUrl: "app/views/adminlogin.html",
controller: "adminlogin"
})
//panel is the admin section
.when("/panel",{
controller: "panel",
templateUrl: "app/views/panel.html",
})
}])
There's a service that authenticates the user to grant access to the admin panel,
auth service
app.service("auth",["$http","$location",function($http){
let api = (window.location.origin)+"/auth";
this.auth = function () {$http.get(api).then((rez)=>{
//is session is not set on server
if(!rez.data.state){
window.location.href = "#/adminlogin"
})}
}])
And finally theres the admin component
app.controller("panel",["$scope","$http","auth",function($scope,$http,auth){
auth.auth();
$scope.addTicket = ()=>{
let bearer = $scope.bearer;
let api = (window.location.origin)+"/addticket";
let postfields = JSON.stringify({"bearer":bearer});
$http.post(api,postfields).then((rez)=>{
console.log(rez);
})
}
}])
This setup is working well and there is restriction for the admin panel, however, when someone attempts to visit the admin panel without logging in, the browser flashes the admin panel template and then quickly reverts back to the admin login. Can someone suggest a way of preventing that awkward flash or even tell me a better way of implementing angularJS routeGuards, thank so much
You need to use resolve in when.
I forked another project and you need to tweak the code as per your requirement. (http://plnkr.co/edit/PAYTAr1sOoCmkP8q)
angular.module('app', ['ngRoute']);
angular.module('app')
.factory("authService", function($q, $timeout){
return {
verify: function(){
var deferred = $q.defer();
$timeout(function(){
// deferred.resolve(true); // if the user is allowed to access admin panel
deferred.reject(false); // if the user is not logged in
},1000);
return deferred.promise;
}
};
});
angular.module('app')
.controller('mainCtrl', function($scope) {
$scope.message = "Message in the main controller";
});
angular.module('app')
.controller('templateCtrl', function($scope) {
$scope.hello = " World";
});
angular.module('app')
.config(function($routeProvider, $locationProvider) {
$routeProvider.
when('/option1', {
templateUrl: 'template.html',
controller: 'templateCtrl',
resolve: {
greeting: function(authService, $location){
return authService.verify().then(function(result){
return true; // if the user is logged in, it will allow the user to access login panel
}, function(result){
$location.path('/login');// if not, it will redirect to login page
return false;
})
}
}
})
.when('/login', {
template: '<div>Login Message</div>'
})
});
Hey Alaksandar Gene i got your answer to work however it was still flashing the template. I guess the problem was from the resolve not awaiting the http call. So i decided to use cookies instead.
app.service("mainauth",function($http,$q,$timeout,$cookies){
this.mainauth = ()=>{
var defer = $q.defer();
let api = (window.location.origin)+"/auth";
if($cookies.get("ticket_admin") == null){
defer.resolve(false);
}
return defer.promise;
}
})
Then for the routing:
app.config(["$routeProvider","$locationProvider",($routeProvider,$locationProvider)=>{
$locationProvider.hashPrefix("");
$routeProvider
.when("/",{
templateUrl: "app/views/enterticket.html"
})
.when("/adminlogin",{
templateUrl: "app/views/adminlogin.html",
controller: "adminlogin"
})
.when("/panel",{
controller: "panel",
templateUrl: "app/views/panel.html",
resolve: {
mainauth: function(mainauth,$location){
mainauth.mainauth().then((rez)=>{
if(!rez){
window.location.href = "#/adminlogin"
}
})
}
}
})
}])
I suppose theres a risk of somebody imitating the cookies, and gaining access so ill put up another service to verify the session incase that happens
app.service("auth",["$http","$cookies",function($http,$cookies){
this.forceAuth = function(){
let url = (window.location.origin)+"/auth";
$http.get(url).then((rez)=>{
if(!rez.data.state){
window.location.href = "#/adminlogin"
}
})
}
}])
I can then inject the above service in the controller for added security.
PS some might wonder why i didnt inject a service that verifies the cookies directly into the controller, thats because it was still causing a flash
i have the following code in my route provider:
project.config(['$routeProvider','$locationProvider',
function ($routeProvider,$locationProvider){
$routeProvider.
when('/start',{
templateUrl: 'start.html',
controller: 'startController'
}).
when('/event',{
templateUrl: 'event.html',
controller: 'eventController'
}).
when('/report',{
templateUrl: 'report.html',
controller: 'reportController'
}).
otherwise({
redirectTo: '/'
});
I have a rootscope variable admin with value of either 1 or 0
My current code sends the user to the start page if an unknown value is given to the route provider. I want the route provider to redirect the user to the event page if the user is an admin or to the start page if the user isnt an admin when an unknown value is given to the route provider.
I was thinking something like this might work, but it didnt.
otherwise({
redirectTo: defaultRedirect
});
$rootScope.admin == 1 ? $rootScope.defaultRedirect = "/event" : $rootScope.defaultRedirect = "/start"
It throws me an error.
Help is appreciated. Thanks.
No it will not work like that. Instead what you can do is have resolve in otherwise block, where you can check if loggedin user is admin or not (by checking $rootScope variable or calling authentication service). If service resolves user being admin then you can use $location service to change route using $location.path("/event") or $location.path("/start"). The code look like:
$routeProvider.otherwise({
resolve: {
load: [
'$rootScope', '$q', 'authentication', '$location', function ($rootScope, $q, authentication, $location) {
var defer = $q.defer();
function chooseRoute() {
if (authentication.identity === undefined) {
$location.path('/logon');
} else {
$location.path('/somewhere');
}
defer.reject();
}
if (authentication.identityResolved)
chooseRoute();
else {
var unbind = $rootScope.$watch(function () {
return authentication.identityResolved;
}, function (resolved) {
if (!resolved)
return;
chooseRoute();
unbind();
});
}
return defer.promise;
}
]
}
});
Another way is, you can check event $routeChangeStart in config/run block & then accordingly checking if user is admin you can redirect user to specific routes using $location service.
I need to restrict the user from redirect and need to login only with authentication.
I tried but I can redirect to login page using back button and again come to same page using forward button. Even I can go to the required page using URL without login.
My code :
config.$inject = ['$routeProvider', '$locationProvider'];
function config($routeProvider, $locationProvider ) {
$routeProvider
.when('/login', {
controller: 'LoginController',
templateUrl: 'view/login.view.html',
controllerAs: 'vm'
})
.when('/profileData', {
controller: 'profileDataController',
templateUrl: 'view/profiledata.view.html',
controllerAs :'vm'
})
.when('/questionBank', {
controller: 'questionbankController',
templateUrl: 'view/questionbank.view.html',
controllerAs: 'vm'
})
.when('/dashboard', {
// controller: 'PersonalInfoController',
templateUrl: 'view/dashboard.view.html',
controllerAs:'vm'
})
.otherwise({ redirectTo: '/login' });
}
run.$inject = ['$rootScope', '$location', '$cookieStore', '$http'];
function run($rootScope, $location, $cookieStore, $http) {
// keep user logged in after page refresh
$rootScope.globals = $cookieStore.get('globals') || {};
if ($rootScope.globals.currentUser) {
$http.defaults.headers.common['Authorization'] = 'Basic ' + $rootScope.globals.currentUser.authdata; // jshint ignore:line
}
$rootScope.$on('$locationChangeStart', function (event, next, current) {
//redirect to login page if not logged in and trying to access a restricted page
var restrictedPage = $.inArray($location.path(), ['/dashboard','/questionBank', '/profileData']) === -1;
/* var a = $location.$$absUrl.split('#')[1];
var patt = new RegExp(a);
var res = patt.test(restrictedPage); */
var loggedIn = $rootScope.globals.currentUser;
if (restrictedPage && !loggedIn) {
$location.path('/login');
}
});
}
use this :based on response from server
.when('/login', {
controller: 'LoginController',
templateUrl: 'view/login.view.html',
resolve:{
logincheck: checklogedin
})
/ resolve function for user....
var checklogedin = function($q ,$http,$location)
{
var deferred =$q.defer();
$http.get('/loggedin').success(function(user){
if (user.staus==true)
{
//goo
deferred.resolve();
}
else
{
deferred.reject();
$location.url('/login');
}
});
return deferred.promise
};
Based on the code that you have provided, I can't tell 100% what is going on in your code. But... you could always try to use the resolve property on each route that you don't want to allow access without authentication. Here is what that would look like for questionBank:
.when('/questionBank', {
controller: 'questionbankController',
templateUrl: 'view/questionbank.view.html',
controllerAs: 'vm',
resolve: {
auth: function(AuthService, $q){
if(AuthService.isAuthenticated()) return $q.resolve();
return $q.reject();
}
}
})
Each property of the resolve object should return a promise, and if that resolves... the route change works. If it rejects... the route change is not allowed. If the promise never resolves, you are screwed, so make sure it resolves or it will never do the route.
This isn't the only way to try what you are saying. It is A way of trying it.
You can also add event listener on your $scope and prevent moving in case of unauthenticated user.
$scope.$on('$locationChangeStart', function (event, next, current) {
if (!is_logged_in) {
event.preventDefault();
}
});
In my code I have two main controllers LoginCtrl and AppCtrl, and all other controllers are nested within AppCtrl. Then in AppCtrl I have this code, which will check for logged user.
if (localStorageService.get('authToken') === null) {
$state.go('login', {locale: CONFIG.defaultLang});
} else if (!userService.isLoggedIn()) {
tokenStorage.setAuthToken(localStorageService.get('authToken'));
userService.setIdentity(JSON.parse(localStorageService.get('user')));
}
As you can see I store auth token from server in local storage. When page loades this code will be executed and if you are not logged in you will be redirected. And because all other application controllers are nested within AppCtrl this code will be executed every time.
For more info about nested controllers try for example this article - https://rclayton.silvrback.com/parent-child-controller-communication
I have two templates as a.html and b.html. Now a.html is for logged in users and b.html for people who are not. Authentication is through an api running on a separate sub-domain. Now i have a call that tells whether a person is authenticated in scope.
How can i optionally load templates based on that. One method is i load b.html and conditionally redirect authenticated users but that is not something i am looking for.
Here is my ngRoute codes
var app = angular.module('app',['ngRoute']);
app.config(function ($routeProvider,$locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/',{
templateUrl : '/templates/b.html',
controller : 'mainController'
})
.otherwise({
templateUrl : 'templates/404.html',
controller : 'mainController'
});
});
app.controller('mainController', function($scope) {
});
The following code could somehow satisfy your requirements but it is in fact a bit tricky since i used ng-include instead of directly setting the templateUrl.
The key is to use the resolve param in $routeProvider. when u pass a $promise as a member in it, the router will wait for them all to be resolved.
app.controller('MainController', ['$scope','loadedTemplate',function ($scope,loadedTemplate) {
//inject the template from the resolve function
$scope.loadedTemplate = loadedTemplate;
}]);
//Service to keep loginStatus
app.factory("loginService",function(){
return {
isLogin:false,
loginStatusFetched:false,
}
})
app.config(['$routeProvider',function ($routeProvider) {
$routeProvider.
when('/', {
template: "<div ng-include='loadedTemplate'></div>",
controller: 'MainController',
resolve:{
loadedTemplate:function($q, $http,$route,loginService) {
console.log("fetching login status");
if(loginService.loginStatusFeteched){
if(loginService.isLogin){
return "a.html"
}
else{
return "b.html"
}
}
console.log("fetching remote authentication");
var deferred = $q.defer();
$http({method: 'GET', url: 'http://jsonplaceholder.typicode.com/posts/1'})
.success(function(data) {
//update the login status here
loginService.loginStatusFeteched = true;
loginService.isLogin = true;
if(loginService.isLogin){
deferred.resolve("a.html");
}
else{
deferred.resolve("b.html");
}
})
.error(function(data){
//error
deferred.resolve("b.html");
});
return deferred.promise;
}
}
})
}]);
I attempted to achieve what u required by setting the templateUrl to a function, but it only takes routeParams, and when the $promise in resolve get resolved, this function has already been executed and there is no way to change the current loaded template.
There is still other choices that you can try. You may use angular-ui-router instead of ng-route to handle state changes, in which u may set child states and present them with the same url conditionally.
But I believe there might be better ways to achieve what you want. If anyone has any better solution, please let me know.
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