How do I limited access to authenticated users? - angularjs

I put together the following controller/service in Angularjs to contact an authentication service. I want to leverage this to restrict other pages of the Angular application to users who have authenticated via this service, but I am not sure how to do that. I am hoping someone can point me in the right direction. Below is my controller and service.
angular.module('App').controller('LoginController', function ($scope, $rootScope, $window, AuthenticationService) {
//function that gets called when the form is submitted.
$scope.submit = function () {
var promise = AuthenticationService.login($scope.user, $scope.password);
promise.then(function () {
var success = AuthenticationService.isAuthenticated();
if (success === true) {
$window.location.href = "/welcome.html";
}
});
}
});
app.service('AuthenticationService', function ($http) {
//Used to track if the user was able to successfully authenticate.
var auth = false;
var token = undefined;
//Had to put this in because I am running into weird behavior when attempting to retrieve a value from response.headers('value');.
//When assiging this to a var, it would always come out as a null value. If you send the value into a function without assigning
//it to a var, the value would be there as expected. For instance, console.log(response.headers('Authorization')); would work, but
//token = response.headers('A‌​uthorization'); would result in a null value. The function below is a workaround.
var getResponseHeader = function (x) { return x; }
//Makes a call to the WEB API Authentication service with the username/password and attempts to authenticate.
var login = function (user, pass) {
var input = { UserName: user, Password: pass };
return $http({
url: "/api/Authentication/Login/{credentials}",
method: "POST",
data: JSON.stringify(input),
dataType: "json"
}).then(function (response) {
if (response.status === 200) { //Call to the service was successful.
//This makes no sense. See comment for getResponseHeader function.
token = getResponseHeader(response.headers('Authorization'));
//If the authentication was successful, 'token' will have a value. If it was not successful, it will be null.
if (token) {
auth = true;
}
}
//There was an error when attempting to authenticate. Alert(response) is there for debugging purposes.
//This will be replaced with a user-friendly error message when completed.
}, function (response) {
alert(response);
});
}
//Logs the user out by removing the token and setting auth back to false
var logout = function (sessionid) {
auth = false;
token = undefined;
}
//Accessor for the 'auth' variable.
var isAuthenticated = function () { return auth; }
//Accessor for the token.
var getToken = function () { return token; }
return {
login: login,
logout: logout,
isAuthenticated: isAuthenticated,
token: getToken
};
});
The service/controller work to authenticate, however anyone can browse to 'welcome.html' or any other page in the application. How do I limit this to users who have successfully authenticated?

I would recommend to use angular-permissions
you set up your states like so:
.state('home', {
url: '/',
templateUrl: 'templates/home.html',
parent: 'parent',
data: {
permissions: { //section for permissions
only: 'isAuthorized', // only allow loged in users
redirectTo: 'login' // if not allowed redirect to login page
}
},
})
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
parent: 'login-parent',
data: {
permissions: {
only: 'anonymous',
redirectTo: 'home'
}
}
})
and set up two groups for authentification:
//A Group for not logged in users
.run(function(PermPermissionStore) {
PermPermissionStore
.definePermission('anonymous', function() {
// Do your authentification here
// you can sett up your funtions elsewere and call them here
// return true if unauthorized
var auth = auth_function()
if (auth === true) {
return false
} else { return true }
})
})
//group for authentificated users
.run(function(PermPermissionStore) {
PermPermissionStore
.definePermission('isAuthorized', function() {
// Do your authentification here
// return true if authorized
var auth = auth_function()
if (auth === true) {
return true
} else { return false }
})
})

On your root app js file run method check for auth value and redirect if not authenticated, the run method on your app.js is a good place to set this watch:
SEE FIDDLE EXAMPLE OF YOUR CODE
.run(['AuthenticationService', '$location', '$rootScope', function (AuthenticationService, $location, $rootScope) {
$rootScope.$watch(function () {
if (!AuthenticationService.isAuthenticated())
$location.path("/login");
return $location.path();
});
}])
This simple watch will check the auth value by calling your isAuthenticated method in the AuthenticationService, so you can be certain no unauthed user will access any page, you should change the logic by adding another condition to check the routes so to only limit access to specifyed pages.

Related

How to prevent state change until value is resolved

I have authentication set up in such a way that I want to prevent any route/state from loading until I know that the user is authorized to access the page. If they are, then the requested page should load, if not, then it should go to the login page.
My config function:
$stateProvider
.state('login', {
url: '/login',
templateUrl : 'login.html',
controller : 'loginController',
data: {
authorizedRoles: [USER_ROLES.guest]
}
})
.state('home', {
url: '/',
templateUrl : 'home.html',
controller : 'homeController',
data: {
authorizedRoles: [USER_ROLES.admin]
}
})
.state('admin', {
url: '/admin',
templateUrl : 'admin.html',
controller : 'adminController',
data: {
authorizedRoles: [USER_ROLES.admin]
}
});
$urlRouterProvider.otherwise('/login');
$locationProvider.html5Mode(true);
My run function:
$rootScope.$on('$stateChangeStart', function(event, next) {
event.preventDefault();
function checkAuthorization() {
if(!AuthService.isAuthorized(authRole)) {
$state.go('login');
} else {
$state.go(next.name);
}
}
if(AuthService.getRoleId() === undefined) {
// We'll have to send request to server to see if user is logged in
AuthService.checkLogin().then(function(response) {
checkAuthorization();
});
} else {
checkAuthorization();
}
})
If I keep the event.preventDefault() in my run function, then the app will be stuck in a loop always going to the requested state. If I remove the event.preventDefault() statement then the app will load the view (which will be visible for a second) before realizing the user should not be allowed to view it (and then go to the correct state).
How can I solve this problem?
You should use resolve and make request to the server to see if user is logged in the resolve
https://github.com/angular-ui/ui-router/wiki#resolve
.state('whatever',{
...
promiseObj: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/someUrl'}).$promise;
},
...
}
OR
if you have make a call in the controller, make the call in resolve in state, in which your api should response with a 401 if user is not login in and redirect to the log in screen if you have an intercept service.
There is detailed explanation how to do this kind of resolve/wait stuff in this Q & A with working plunker.
An extracted version of the Auth service is:
.factory('userService', function ($timeout, $q) {
var user = undefined;
return {
// async way how to load user from Server API
getAuthObject: function () {
var deferred = $q.defer();
// later we can use this quick way -
// - once user is already loaded
if (user) {
return $q.when(user);
}
// server fake call, in action would be $http
$timeout(function () {
// server returned UN authenticated user
user = {isAuthenticated: false };
// here resolved after 500ms
deferred.resolve(user)
}, 500)
return deferred.promise;
},
// sync, quick way how to check IS authenticated...
isAuthenticated: function () {
return user !== undefined
&& user.isAuthenticated;
}
};
})
where the most important parts are
var user = undefined; - "global" variable which is
containing user and can answer if he has rights
does not contain user (yet) and the answer is "not authorized" then
returned service function: isAuthenticated
That should solve the issue. check more details here

Checking if a user is authenticated on the back-end in Node.js, and serving page with ui-router in Angular

I can't find any resources online for my particular case.
On the back-end I am using PassportJS in NodeJS to authenticate users.
When the user requests the index page, I want him to see his home page (similar to how facebook.com takes you to your personal feed) or a login page if he is not authenticated.
Instead of the server handling which page to send back, as I don't want the user to feel redirected, I want Angular UI-Router to handle it.
That is, when the user requests a page, I want UI-Router to handle which pages he sees.
Is there any way to do that? Preferably, a very simple way.
Yes you can use resolve in ui-router.
Lets say you have an Auth Service (in example, i am using $firebaseSimpleLogin) You can define your on factory.
.factory('Auth', function ($firebaseSimpleLogin, FIREBASE_URL, $rootScope, $firebase) {
var ref = new Firebase(FIREBASE_URL);
var auth = $firebaseSimpleLogin(ref);
var Auth = {
register: function (user) {
return auth.$createUser(user.email, user.password);
},
createProfile: function (user) {
var profile = {
username: user.username,
md5_hash: user.md5_hash
};
var profileRef = $firebase(ref.child('profile'));
return profileRef.$set(user.uid, profile);
},
login: function (user) {
return auth.$login('password', user);
},
logout: function () {
auth.$logout();
},
resolveUser: function() {
return auth.$getCurrentUser();
},
signedIn: function() {
return !!Auth.user.provider;
},
user: {}
};
$stateProvider
.state('TestPageCtrl', {
url: 'testpage',
templateUrl: 'test/testpage.html',
controller: 'AuthCtrl',
resolve: {
user: function(Auth) {
return Auth.resolveUser();
}
}
});
So now using this user you can control access of user.
.controller('TestPageCtrl', function ($scope, $state, Auth, user) {
if (user) {
$state.go('posts');
} else {
// redirect to a page saying that login first.
}
});

How to direct a user to a new route after login?

A user logs in on the / route.
How do I direct them away from the / view of my app
/packages/system/public/views/index.html
to another view /app?
/packages/system/public/views/app.html
I want this view /app to be secure, so only logged in users can access it. Non-logged in users should be sent back to /.
In /packages/users/controllers/meanUser.js
// Register the login() function
$scope.login = function() {
$http.post('/login', {
email: $scope.user.email,
password: $scope.user.password
})
.success(function(response) {
// authentication OK
$scope.loginError = 0;
$rootScope.user = response.user;
$rootScope.$emit('loggedin');
if (response.redirect) {
if (window.location.href === response.redirect) {
//This is so an admin user will get full admin page
window.location.reload();
} else {
window.location = response.redirect;
}
} else {
// Redirect Here
$location.url('/');
$location.url('/articles'); // Will take you to the articles view after login
}
})
.error(function() {
$scope.loginerror = 'Authentication failed.';
});
};
If you need a user to be redirected to another page when they attempt to access a secured route without being logged in you can refer to the code in /packages/articles/public/routes/articles.js
// This function checks if the user is logged in and redirects to the login page.
var checkLoggedin = function($q, $timeout, $http, $location) {
// Initialize a new promise
var deferred = $q.defer();
// Make an AJAX call to check if the user is logged in
$http.get('/loggedin').success(function(user) {
// Authenticated
if (user !== '0') $timeout(deferred.resolve);
// Not Authenticated
else {
$timeout(deferred.reject);
$location.url('/login');
}
});
return deferred.promise;
};
// These are your defined routes.
$stateProvider
.state('all articles', {
url: '/articles',
templateUrl: 'articles/views/list.html',
// This resolve runs the checkLoggedin function as the route is accessed
// and redirects if the user isn't logged in.
resolve: {
loggedin: checkLoggedin
}
});

Angular js and ionic

how do i post html login form to php login form in a different url using angular js controller and returning success when the login credential is correct and failure when the credentials are wrong.below is my existing Controller and intends to use it together with my html login page while it posts/authenticates the login.php and returns success or failure upon input credentials
.controller('LoginCtrl', function ($scope, $state, $ionicViewService, $http, DataStore) {
$scope.domain = DataStore.domain;
var urlpath = DataStore.domain+'/login.php';
$("#username").focus();
$("#username, #password").keyup(function () {
if ($(this).val().length !== 0) {
$("#validate").hide();
}
});
//Authenticates blank fields
$("#login").on('click', function () {
if ($("#username").val() == '') {
$("#validate").html("username is required").show();
$("#username").focus();
}
else if ($("#password").val() == '') {
$("#validate").html("Password is required").show();
$("#password").focus();
}
else {
$.ajax({
type: "POST",
url: urlpath,
data: $('#myloginform').serialize(),
success: function (html) {
var resp = html.split(":");
// alert(resp[0]);
if (resp[0] == 'success') {
$("#validate").html("Wrong username or password").show();
}else {
$state.go('menu.home');
}
}
});
// todo: Login is actually done here
//todo: validate the login
$ionicViewService.nextViewOptions({
disableBack: true
});
//$state.go('menu.home');
return false;
};
})
})
Have you tried coding an
else {
$http.post('http://localhost:0000', data).success(successCallback);
}
Localhost would be the ip or url of the server you are wishing to send the post data to.

Angular/Breeze Login implementation

I have a SPA app that uses Angular and Breeze, I need to implement the login functionality and I am new to Angular/Breeze. My architecture/code structure is as mentioned below:
login.html --> login.js -->datacontext/Service.js--->entityManagerFactory-->breezecontroller.cs -->repository->dbcontext-->database.
I am facing following challenges:
I am unable to show the login page as default, I am always getting Dashboard as a default page. I am looking for where I can route to login page.
2.breezecontroller -- This is inside controller, do I need to write my login method here?
All in all, I am looking for a complete login functionality implementation which following my architecture/code structure.
Here is a description of an approach that can be used in an Angular-based SPA. This particular example uses token-based OAuth authentication, but could be adapted to other authentication schemes. It is loosely based on the approach described at Authentication in AngularJS (or similar) based application
Some highlights are:
Authentication is managed through an auth service.
HTTP requests are intercepted, and:
When a 401 (access denied) error is detected and no user is logged in, an auth:login event is emitted (note - not broadcasted) on $rootScope
If a 401 error is detected while a user is logged in and an OAuth refresh token is available, an attempt is made to get a new access token based on the refresh token. An auth:login event is only emitted if the token cannot be refreshed.
Once a user has logged in, an Authorization header containing the user's access token is inserted onto each HTTP request so that the server can authenticate the user.
The application should watch for auth:login events and prompt the user for credentials. (I use an Angular-UI Bootstrap modal dialog for doing this.) Once credentials have been provided, the auth service's login function must be called to complete the login. After login is called, all pending HTTP requests that initially failed with a 401 error are retried. Alternatively, the auth service's loginCancelled function can be called to cancel the login, which will reject all pending HTTP requests.
For example:
angular.module('app', ['auth'])
.run(['$rootScope', 'auth', function ($rootScope, auth) {
$rootScope.$on(auth.options.loginRequiredEvent, function (event, details) {
// Display login dialog here, which will ultimately
// call `auth.login` or `auth.loginCancelled`
});
auth.restoreAuthDataFromStorage();
}]);
Here is an example of calling auth.login once the user has provided credentials:
auth.login(userName, password, isPersistent)
.success(function () {
// Dismiss login dialog here
})
.error(function (data, status) {
if (status === 401 || (data && data.error === 'invalid_grant')) {
failureMessage = 'Log in failed: Bad username or password';
} else {
failureMessage = 'Log in failed: Unexpected error';
}
});
Details of the logged in user are stored in window.sessionStorage or window.localStorage (based on whether a persistent login has been requested) to be able to be accessed across page loads.
Finally, here is the auth service itself.
var module = angular.module('auth');
module.provider('auth', function () {
var authOptions = {
tokenUrl: '/OAuthToken',
loginRequiredEvent: 'auth:loginRequired',
logoffEvent: 'auth:logoff',
loginEvent: 'auth:login',
authTokenKey: 'auth:accessToken'
};
this.config = function (options) {
angular.extend(authOptions, options);
};
// Get the auth service
this.$get = ['$rootScope', '$http', '$q', function ($rootScope, $http, $q) {
var authData = {
// Filled as follows when authenticated:
// currentUserName: '...',
// accessToken: '...',
// refreshToken: '...',
};
var httpRequestsPendingAuth = new HttpRequestsPendingAuthQueue();
// Public service API
return {
login: login,
refreshAccessToken: refreshAccessToken,
loginCancelled: loginCancelled,
logoff: logoff,
currentUserName: function () { return authData.currentUserName; },
isAuthenticated: function () { return !!authData.accessToken; },
getAccessToken: function () { return authData.accessToken; },
restoreAuthDataFromStorage: restoreAuthDataFromStorage,
_httpRequestsPendingAuth: httpRequestsPendingAuth,
options: authOptions,
};
function isAuthenticated() {
return !!authData.accessToken;
};
function restoreAuthDataFromStorage() {
// Would be better to use an Angular service to access local storage
var dataJson = window.sessionStorage.getItem(authOptions.authTokenKey) || window.localStorage.getItem(authOptions.authTokenKey);
authData = (dataJson ? JSON.parse(dataJson) : {});
}
function accessTokenObtained(data) {
if (!data || !data.access_token) {
throw new Error('No token data returned');
}
angular.extend(authData, {
accessToken: data.access_token,
refreshToken: data.refresh_token
});
// Would be better to use an Angular service to access local storage
var storage = (authData.isPersistent ? window.localStorage : window.sessionStorage);
storage.setItem(authOptions.authTokenKey, JSON.stringify(authData));
httpRequestsPendingAuth.retryAll($http);
}
function login(userName, password, isPersistent) {
// Data for obtaining token must be provided in a content type of application/x-www-form-urlencoded
var data = 'grant_type=password&username=' + encodeURIComponent(userName) + '&password=' + encodeURIComponent(password);
return $http
.post(authOptions.tokenUrl, data, { ignoreAuthFailure: true })
.success(function (data) {
authData = {
currentUserName: userName,
isPersistent: isPersistent
};
accessTokenObtained(data);
$rootScope.$emit(authOptions.loginEvent);
})
.error(function () {
logoff();
});
}
function refreshAccessToken() {
if (!authData.refreshToken) {
logoff();
return $q.reject('No refresh token available');
}
// Data for obtaining token must be provided in a content type of application/x-www-form-urlencoded
var data = 'grant_type=refresh_token&refresh_token=' + encodeURIComponent(authData.refreshToken);
return $http
.post(authOptions.tokenUrl, data, { ignoreAuthFailure: true })
.success(function (data) { accessTokenObtained(data); })
.error(function () { logoff(); });
}
function loginCancelled() {
httpRequestsPendingAuth.rejectAll();
}
function logoff() {
// Would be better to use an Angular service to access local storage
window.sessionStorage.removeItem(authOptions.authTokenKey);
window.localStorage.removeItem(authOptions.authTokenKey);
if (isAuthenticated()) {
authData = {};
$rootScope.$emit(authOptions.logoffEvent);
}
}
// Class implementing a queue of HTTP requests pending authorization
function HttpRequestsPendingAuthQueue() {
var q = [];
this.append = function (rejection, deferred) {
q.push({ rejection: rejection, deferred: deferred });
};
this.rejectAll = function () {
while (q.length > 0) {
var r = q.shift();
r.deferred.reject(r.rejection);
}
};
this.retryAll = function ($http) {
while (q.length > 0) {
var r = q.shift();
retryRequest($http, r.rejection.config, r.deferred);
}
};
function retryRequest($http, config, deferred) {
var configToUse = angular.extend(config, { ignoreAuthFailure: true });
$http(configToUse)
.then(function (response) {
deferred.resolve(response);
}, function (response) {
deferred.reject(response);
});
}
}
}];
});
module.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(['$injector', '$rootScope', '$q', function ($injector, $rootScope, $q) {
var auth;
return {
// Insert an "Authorization: Bearer <token>" header on each HTTP request
request: function (config) {
auth = auth || $injector.get('auth');
var token = auth.getAccessToken();
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = 'Bearer ' + token;
}
return config;
},
// Raise a "login required" event upon "401 access denied" responses on HTTP requests
responseError: function(rejection) {
if (rejection.status === 401 && !rejection.config.ignoreAuthFailure) {
var deferred = $q.defer();
auth = auth || $injector.get('auth');
auth._httpRequestsPendingAuth.append(rejection, deferred);
if (auth.isAuthenticated()) {
auth.refreshAccessToken().then(null, function () {
$rootScope.$emit(auth.options.loginRequiredEvent, { message: 'Login session has timed out. Please log in again.' });
});
} else {
// Not currently logged in and a request for a protected resource has been made: ask for a login
$rootScope.$emit(auth.options.loginRequiredEvent, { rejection: rejection });
}
return deferred.promise;
}
// otherwise, default behaviour
return $q.reject(rejection);
}
};
}]);
}]);

Resources