What is Authentication?
Authentication
is the process of identifying a user that wants to access a protected resource.
We use authentication is our everyday lives: ID cards, user names and
passwords, security cards, etc. The process that comes next is called
‘Authorization’ and they are very strong related and sometimes confused. With
‘Authorization’, we can check for user rights and see if they have or not
access to a specific resource after they have been authenticated. However, make
no mistake; there could be no ‘Authorization’ without ‘Authentication’. In this
article we will discuss about the process of identifying ‘who is this user’
using AngularJS.
Considerations
Remember
that all actions take place on the client side, which means that the client has
full control over the browser and can overpass security checks. Therefore, it
is very important to make the verification on the back-end also.
Setting up client-side
Routing
Here we can
decide which pages will need authentication and set the routing for the
application. A route is defined providing at least the template or templateUrl
and the controller of that page. In addition, I have added the ‘access’
property with ‘allowAnonymous’ attribute. This way we now if the current route
needs authentication or it is a free access page. In our example we have the
‘Login’ page which is accessible by anyone and the ‘MembersPage’ that needs
authentication.
// in app.js
var myApp =
angular.module('myApp',['ngResource', 'ngCookies', 'ngRoute']); myApp.config(function ($httpProvider,
$routeProvider) {
window.routes = {
'/Login':
{
templateUrl: '/Account/Login',
controller: 'AccountController',
access : {allowAnonymous : true}
},
'/MembersPage:
{
templateUrl: '/Home/SomePage,
controller: SomePageController',
access: {allowAnonymous:false}}};
for (var path in
window.routes) {
$routeProvider.when(path,
window.routes[path]);
}
$routeProvider.otherwise({ redirectTo: '/Login' });
});
The next
thing to do is to recognize an authenticated user and check if it has access to
the routes.
Recognize an authenticated
user
There are
several ways for doing that but I prefer using the power of AngularJS
throughout the use of ‘Services’. Therefore, I have created a ‘UserService’
where we store the current user name and a value-indicating if is authenticated
or not.
// in UserService.js
myApp.factory('userService', function () {
var user
= {
isLogged: false,
username: '',
};
var
reset = function() {
user.isLogged = false;
user.username = '';
};
return {
user: user,
reset : reset
};
});
After we
have the service in place, it is time to use it and implement the check
functionality for a route. There are several methods that intercept the route
change event, but we are interested only in those that occur before the user
was redirected so we can check if is authenticated: ‘$routeChangeStart’, ‘$locationChangeStart’. Here we can check if the route
that the user is going to allows anonymous access and if the user is logged in.
If the case of failure, we can display an error message and redirect the user
to the login page.
// in
RootController.js
myApp.controller('RootController',
function
($scope, $route, $routeParams, $location, $rootScope, authenticationService, userService,
toaster) {
$scope.user = userService.user;
$scope.$on('$routeChangeStart', function (e, next, current) {
if
(next.access != undefined && !next.access.allowAnonymous &&
!$scope.user.isLogged) {
$location.path("/Login");
}
});
$scope.logout = function () {
authenticationService.logout()
.success(function (response) {
userService.reset();
toaster.pop("info", 'You are logged out.', '');
});
};
$rootScope.$on("$locationChangeStart",
function (event, next, current) {
for (var i
in window.routes) {
if
(next.indexOf(i) != -1) {
if
(!window.routes[i].access.allowAnonymous && !userService.user.isLogged)
{
toaster.pop("error", 'You are not logged in!', '');
$location.path("/Login");
}
}
}
});
});
Authentication Service
This
service provides a way of communicating with the server and sets up the login
status. We are interested in login/logout methods for the moment. On the
back-end it is used a Web API service.
The login
method is a post request sending the ‘login’ data consisting of the username
and password. Notice also the ‘RequestVerificationToken’ that is used to avoid cross-site
request forgery attacks.
//in AuthenticationService.js
myApp.factory('authenticationService',
function
($http, $log, $location) {
return {
login: function (login, antiForgeryToken) {
return $http({
method: 'POST',
url: '/api/Account/AuthenticateUser',
data: login,
headers: { 'RequestVerificationToken': antiForgeryToken }
});
},
logout: function () {
return $http.post('/api/Account/Logout');
}
};
});
Putting up all together
The only
thing that remains is to create a view to collect login information and a
controller where we can use the authentication service and the user service
together.
A very
simple login form would look like the example below. We have three input fields
wrapped up in a form.
//in Login.cshtml
@model Model.LoginModel
@{ Layout = null; }
@functions {
private
String GetAntiForgeryToken()
{
string
cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return
cookieToken + ":" + formToken;
}
}
<div class="container">
<form
name="loginForm" class="form-signin">
<input id="antiForgeryToken"
data-ng-model="antiForgeryToken"
data-ng-init="antiForgeryToken='@GetAntiForgeryToken()'"
type="hidden"/>
<h2
class="form-signin-heading">Authentication</h2>
<br
/>
<input type="email" required="required"
name="username" class="form-control"
ng-model="userData.username" placeholder="Email
address" />
<input type="password" required name="password"
class="form-control"
ng-model="userData.password" placeholder="Password"
/>
<label class="checkbox">
<input type="checkbox"
value="remember-me">Remember me</label>
<button type="submit" class="btn btn-lg btn-primary
btn-block" ng-click="login(userData,
loginForm)">Login</button>
</form>
</div>
When the
user clicks login button, the form data is sent to the account controller.
Using the authentication service, the data is sent in a POST request. In case
of success we use the user service to store the data and redirect the user to the
desired page, otherwise we show an error message and keep the user on the login
page.
// in
AccountController.js
myApp.controller('AccountController',
function
AccountController($scope, $cookies, $log, $location, authenticationService,
toaster, userService) {
$scope.login = function (userData, loginForm) {
if
(loginForm.$valid) {
authenticationService.login(userData, $scope.antiForgeryToken)
.success(function (response) {
if (response.status) {
userService.user.username = response.data.userName;
userService.user.isLogged = response.data.isLogged;
toaster.pop('success', 'You are signed in!', '', 2000, true);
$location.path("/MembersPage");
} else
{
toaster.pop('error', 'Invalid username or password!', '', 2000, true);}
}).error(function (data, status, headers, config)
{
$log.info(data);});}
};
});
This is all
it takes to have a rudimentary authentication implementation with AngularJS.