You might wonder why we are covering security in a book on AngularJS. Well, quite simply, security is one of the most important and most challenging tasks faced by an AngularJS developer. It’s not that the developer is actually responsible for implementing the security layer—that is not the case at all—but it is very important for an AngularJS developer to understand the role that AngularJS plays in the overall security model of an application or website.
You should never attempt to implement an independent client-side security layer in an AngularJS application, or any other JavaScript application for that matter. Security should always be implemented on the backend services where the data resides. That is the only safe place to implement a security layer.
Remember the user has full access to the JavaScript running in the browser. As I said before, our AngularJS application runs in the user’s browser on the user’s hardware. The user can save the JavaScript locally and easily make modifications circumventing any security layer implemented by an unsuspecting JavaScript developer.
With that in mind, there are several rules that AngularJS developers and backend developers need to remember. Although actually implementing the security layer is not usually the job of an AngularJS developer, it is often a collaborative effort for all developers involved in a project. The following rules should always be considered:
*
as the list of allowed domains. For example, (Access-Control-Allow-Origin: *)
would allow all domains to make cross-origin calls to the REST services on the site. Doing that circumvents the browser’s CORS security implementation completely.Always remember that any security-related JavaScript code can be viewed and modified by the user. While most modern browsers do offer built-in security, JavaScript developers should never rely on the browser for security. The responsibility for security rests entirely on the shoulders of the backend service developers. With that said, I will show some techniques for developing AngularJS applications that work well with a security layer implemented properly in the backend services.
We will start our discussion of security by building a login screen and the associated controller and service for our blog application. We will send the user’s credentials to a login REST service for validation. We will also make use of the business logic services that we developed back in Chapter 8.
We don’t actually use HTTPS for our blog application because it’s not a production application. But in a production environment, SSL should always be used to protect private data and the user’s credentials when calling a login REST service. Additional security steps could even be taken in the REST services to limit access to a particular machine or a particular IP address. We will not, however, be concerned with that level of security in this book.
We will start off by adding an AngularJS login service. Open your editor and add the following code to the bottom of your project’s services.js file. The new AngularJS login service maps to a login REST service on our backend server. The code is much like that of the other AngularJS services we’ve set up so far. It has one method, login
, that maps to a POST
method on the REST service:
/* chapter10/services.js excerpt */
blogServices
.
factory
(
'Login'
,
[
'$resource'
,
function
(
$resource
)
{
return
$resource
(
"http://nodeblog-micbuttoncloud.rhcloud.com/NodeBlog/login"
,
{},
{
login
:
{
method
:
'POST'
,
cache
:
false
,
isArray
:
false
}
});
}]);
Now we need to add a login controller. Open your editor and add the code shown next to the bottom of the controllers.js file. Notice that we inject the new Login
service and the setCreds
business logic service that we developed back in Chapter 8. We also inject the $location
service to allow us to redirect the user once authenticated. The new controller has a submit
method that is attached to the scope. Attaching the method to the scope allows us to call the method from inside the login template. We build the JSON request that gets passed to the service in the variable named postData
, using the scope properties submitted by the form:
/* chapter10/controllers.js excerpt */
blogControllers
.
controller
(
'LoginCtrl'
,
[
'$scope'
,
'$location'
,
'Login'
,
'setCreds'
,
function
LoginCtrl
(
$scope
,
$location
,
Login
,
setCreds
)
{
$scope
.
submit
=
function
(){
$scope
.
sub
=
true
;
var
postData
=
{
"username"
:
$scope
.
username
,
"password"
:
$scope
.
password
};
Login
.
login
({},
postData
,
function
success
(
response
)
{
console
.
log
(
"Success:"
+
JSON
.
stringify
(
response
));
if
(
response
.
authenticated
){
setCreds
(
$scope
.
username
,
$scope
.
password
)
$location
.
path
(
'/'
);
}
else
{
$scope
.
error
=
"Login Failed"
}
},
function
error
(
errorResponse
)
{
console
.
log
(
"Error:"
+
JSON
.
stringify
(
errorResponse
));
}
);
};
}]);
We also add a scope property named error
. This property is populated any time the user fails to authenticate, displaying a “Login Failed” message. We will see how the error is presented later in the chapter. Once the user authenticates, we make a call to the AngularJS business logic service setCreds
and pass the user’s username and password to be saved in a cookie. We then redirect the user to the main application link.
We must also make minor modifications to the other two controllers in our blog project. Open your editor and replace the two controllers added earlier with the code shown next. Notice we now inject the $location
service and the checkCreds
business service that we added back in Chapter 8. The checkCreds
service works by checking the user’s credentials at the top of the controller. If the user has not authenticated, a call is made to the path
method on the $location
service to redirect the user to the login page (we will cover the new login path shortly):
/* chapter10/controllers.js excerpt */
blogControllers
.
controller
(
'BlogCtrl'
,
[
'$scope'
,
'BlogList'
,
'$location'
,
'checkCreds'
,
function
BlogCtrl
(
$scope
,
BlogList
,
$location
,
checkCreds
)
{
if
(
!
checkCreds
()){
$location
.
path
(
'/login'
);
}
BlogList
.
get
({},
function
success
(
response
)
{
console
.
log
(
"Success:"
+
JSON
.
stringify
(
response
));
$scope
.
blogList
=
response
;
},
function
error
(
errorResponse
)
{
console
.
log
(
"Error:"
+
JSON
.
stringify
(
errorResponse
));
}
);
}]);
blogControllers
.
controller
(
'BlogViewCtrl'
,
[
'$scope'
,
'$routeParams'
,
'BlogPost'
,
'$location'
,
'checkCreds'
,
function
BlogViewCtrl
(
$scope
,
$routeParams
,
BlogPost
,
$location
,
checkCreds
)
{
if
(
!
checkCreds
()){
$location
.
path
(
'/login'
);
}
var
blogId
=
$routeParams
.
id
;
BlogPost
.
get
({
id
:
blogId
},
function
success
(
response
)
{
console
.
log
(
"Success:"
+
JSON
.
stringify
(
response
));
$scope
.
blogEntry
=
response
;
},
function
error
(
errorResponse
)
{
console
.
log
(
"Error:"
+
JSON
.
stringify
(
errorResponse
));
}
);
}]);
We have one more change to make to the controllers.js file: we need to add a new controller to log the user out of the system and reset his credentials. Add the code shown here to the bottom of the controllers.js file. Once again, we make use of the AngularJS business logic services written back in Chapter 8 by adding a call to the deleteCreds
service. The service call removes the user’s credentials, and then we redirect the user to the login page:
/* chapter10/controllers.js excerpt */
blogControllers
.
controller
(
'LogoutCtrl'
,
[
'$location'
,
'deleteCreds'
,
function
LogoutCtrl
(
$location
,
deleteCreds
)
{
deleteCreds
();
$location
.
path
(
'/login'
);
}]);
The entire controllers.js file is shown here to help make the changes clearer:
/* chapter10/controllers.js */
'use strict'
;
/* Controllers */
var
blogControllers
=
angular
.
module
(
'blogControllers'
,
[]);
blogControllers
.
controller
(
'BlogCtrl'
,
[
'$scope'
,
'BlogList'
,
'$location'
,
'checkCreds'
,
function
BlogCtrl
(
$scope
,
BlogList
,
$location
,
checkCreds
)
{
if
(
!
checkCreds
()){
$location
.
path
(
'/login'
);
}
BlogList
.
get
({},
function
success
(
response
)
{
console
.
log
(
"Success:"
+
JSON
.
stringify
(
response
));
$scope
.
blogList
=
response
;
},
function
error
(
errorResponse
)
{
console
.
log
(
"Error:"
+
JSON
.
stringify
(
errorResponse
));
});
}]);
blogControllers
.
controller
(
'BlogViewCtrl'
,
[
'$scope'
,
'$routeParams'
,
'BlogPost'
,
'$location'
,
'checkCreds'
,
function
BlogViewCtrl
(
$scope
,
$routeParams
,
BlogPost
,
$location
,
checkCreds
)
{
if
(
!
checkCreds
()){
$location
.
path
(
'/login'
);
}
var
blogId
=
$routeParams
.
id
;
BlogPost
.
get
({
id
:
blogId
},
function
success
(
response
)
{
console
.
log
(
"Success:"
+
JSON
.
stringify
(
response
));
$scope
.
blogEntry
=
response
;
},
function
error
(
errorResponse
)
{
console
.
log
(
"Error:"
+
JSON
.
stringify
(
errorResponse
));
});
}]);
blogControllers
.
controller
(
'LoginCtrl'
,
[
'$scope'
,
'$location'
,
'Login'
,
'setCreds'
,
function
LoginCtrl
(
$scope
,
$location
,
Login
,
setCreds
)
{
$scope
.
submit
=
function
(){
$scope
.
sub
=
true
;
var
postData
=
{
"username"
:
$scope
.
username
,
"password"
:
$scope
.
password
};
Login
.
login
({},
postData
,
function
success
(
response
)
{
console
.
log
(
"Success:"
+
JSON
.
stringify
(
response
));
if
(
response
.
authenticated
){
setCreds
(
$scope
.
username
,
$scope
.
password
)
$location
.
path
(
'/'
);
}
else
{
$scope
.
error
=
"Login Failed"
}
},
function
error
(
errorResponse
)
{
console
.
log
(
"Error:"
+
JSON
.
stringify
(
errorResponse
));
});
};
}]);
blogControllers
.
controller
(
'LogoutCtrl'
,
[
'$location'
,
'deleteCreds'
,
function
LogoutCtrl
(
$location
,
deleteCreds
)
{
deleteCreds
();
$location
.
path
(
'/login'
);
}]);
Next, we will added a new login template and the associated CSS. We will then add two new paths to the $routeProvider
section of the app.js file.
Right-click the project node and add a new HTML file to the partials folder. Name the new file login.html. Replace the content of the newly created file with the code shown here. Notice that we use the ng-submit
directive to connect the submit
method in our LoginCtrl
to the form for form submission:
<!-- chapter10/login.html -->
<div
class=
"blog-login-wrapper"
>
<form
class=
""
ng-submit=
"submit()"
ng-controller=
"LoginCtrl"
>
<div
class=
"blog-login-error"
>
{{error}}</div>
<div
class=
"blog-login-label"
>
<label
for=
"username"
>
Username:</label></div>
<div
class=
"blog-login-element"
>
<input
type=
"text"
ng-model=
"username"
name=
"username"
placeholder=
"username"
required
/></div>
<div
class=
"blog-login-label"
>
<label
for=
"password"
>
Password:</label></div>
<div
class=
"blog-login-element"
>
<input
type=
"password"
ng-model=
"password"
name=
"password"
placeholder=
"password"
required
/></div>
<div
class=
"blog-login-button"
>
<button
type=
"submit"
class=
"form-button"
>
Sign in</button></div>
</form>
</div>
Now open the CSS file styles.css in your editor and add the following code to the bottom of the file. Notice that we use CSS3 media queries like @media screen
and (min-width: 1200px)
to make our login template be responsive and look good on any mobile or desktop platform:
/* chapter10/styles.css */
.blog-login-wrapper
{
float
:
left
;
background
:
#e0e0e0
;
border-radius
:
6px
;
-
moz
-
border-radius
:
6px
;
/* Firefox 3.6 and earlier */
border
:
darkgreen
solid
1px
;
}
@media
screen
and
(
min-width
:
1200px
)
{
.blog-login-wrapper
{
width
:
40%
;
margin
:
10%
0
0
30%
;
padding
:
1%
;
background
:
#e0e0e0
;
border-radius
:
6px
;
-
moz
-
border-radius
:
6px
;
/* Firefox 3.6 and earlier */
border
:
darkgreen
solid
1px
;
}
}
@media
screen
and
(
max-width
:
1200px
)
{
.blog-login-wrapper
{
width
:
40%
;
margin
:
10%
0
0
30%
;
padding
:
1%
;
background
:
#e0e0e0
;
border-radius
:
6px
;
-
moz
-
border-radius
:
6px
;
/* Firefox 3.6 and earlier */
border
:
darkgreen
solid
1px
;
}
}
@media
screen
and
(
max-width
:
600px
)
{
.blog-login-wrapper
{
width
:
80%
;
margin
:
10%
0
0
10%
;
padding
:
1%
;
background
:
#e0e0e0
;
border-radius
:
6px
;
-
moz
-
border-radius
:
6px
;
/* Firefox 3.6 and earlier */
border
:
darkgreen
solid
1px
;
}
}
.blog-login-label
{
float
:
left
;
width
:
70%
;
margin
:
0
0
0
15%
;
padding
:
1%
0
0
0
;
text-align
:
center
;
}
.blog-login-element
{
float
:
left
;
width
:
70%
;
margin
:
0
0
0
15%
;
padding
:
1%
0
0
0
;
text-align
:
center
;
}
.blog-login-button
{
float
:
left
;
width
:
100%
;
margin
:
0
0
0
0
;
padding
:
5%
0
0
0
;
text-align
:
center
;
}
.blog-login-error
{
float
:
left
;
width
:
100%
;
margin
:
0
0
0
0
;
padding
:
0
0
0
0
;
text-align
:
center
;
color
:
red
;
}
Now we need to add the two new routes to our route provider in the app.js file. The following code shows the changes needed for this file. As you can see, the two new routes make use of the two new controllers and the new template file:
/* chapter10/app.js */
'use strict'
;
/* App Module */
var
blogApp
=
angular
.
module
(
'blogApp'
,
[
'ngRoute'
,
'blogControllers'
,
'blogServices'
,
'blogBusinessServices'
,
'blogDirectives'
]);
blogApp
.
config
([
'$routeProvider'
,
'$locationProvider'
,
function
(
$routeProvider
,
$locationProvider
)
{
$routeProvider
.
when
(
'/'
,
{
templateUrl
:
'partials/main.html'
,
controller
:
'BlogCtrl'
}).
when
(
'/blogPost/:id'
,
{
templateUrl
:
'partials/blogPost.html'
,
controller
:
'BlogViewCtrl'
}).
when
(
'/login'
,
{
templateUrl
:
'partials/login.html'
,
controller
:
'LoginCtrl'
}).
when
(
'/logOut'
,
{
templateUrl
:
'partials/login.html'
,
controller
:
'LogoutCtrl'
});
$locationProvider
.
html5Mode
(
false
).
hashPrefix
(
'!'
);
}]);
Finally, we need to make one more change to our blog application: we need to modify the menu.html file and add the new “Logout” menu link. Here is the line you’ll need to add to the menu.html file. The new logout link maps to the logout route that we just added:
<!-- chapter10/menu.html excerpt -->
<li><a
id=
"lo"
href=
"#!logOut"
>
Logout</a></li>
The complete menu.html file is shown here for convenience:
<!-- chapter10/menu.html complete file -->
<nav
class=
"navbar navbar-inverse navbar-fixed-top"
role=
"navigation"
>
<!-- Brand and toggle get grouped for better mobile display -->
<div
class=
"container"
>
<div
class=
"navbar-header"
>
<button
type=
"button"
class=
"navbar-toggle"
data-toggle=
"collapse"
data-target=
".navbar-collapse"
>
<span
class=
"sr-only"
>
Toggle navigation</span>
<span
class=
"icon-bar"
></span>
<span
class=
"icon-bar"
></span>
<span
class=
"icon-bar"
></span>
</button>
<a
class=
"navbar-brand"
style=
"{{brandColor}}"
href=
"#!/"
>
{{label}}</a>
</div>
<!--Collect the nav links, forms, and other content for toggling -->
<div
class=
"collapse navbar-collapse"
>
<ul
class=
"nav navbar-nav"
>
<li
class=
"{{aboutActiveClass}}"
><a
href=
"#!about"
>
About</a></li>
<li
class=
""
>
<a
href=
"https://github.com/KenWilliamson"
>
Download Project Code</a>
</li>
<li><a
id=
"lo"
href=
"#!logOut"
>
Logout</a></li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
</nav>
Once you have made all the changes outlined in this chapter, your blog application should have all the needed security additions that were specified. To test the changes that were made, we will run the project and check for errors.
Right-click the project node and select “Run” from the menu. Your project should run and you should see the screen in Figure 10-1. If you do not see the login screen, check that all the changes outlined in this chapter were performed correctly. Turn on developer tools for your browser and look for errors, as described in previous chapters.
Once your project is running, do the following:
You should now see the same blog screens that you built in the previous chapters. The application should function just as before with no changes. Navigate through the application to validate that everything works correctly.
If you were to enter incorrect user credentials, you would see the error message described earlier (“Login Failed”) displayed in red. Notice the new menu item “Logout” at the right end of the menu bar. Click “Logout” and your session should end. You should then be taken back to the login screen. If the login and logout process work correctly, your security changes were implemented successfully.
We’ve added a new AngularJS service and two new controllers to our blog application. We now need to test the application to make certain there are no defects in our code. We also need to validate that all previous unit tests are still passing.
We will start off by writing a test specification for the new service. We will then write two new test specifications for the two new controllers. Once our unit testing is complete, we will make changes to our end-to-end testing.
We already have an up-to-date Karma configuration file for our blog project. There should be no changes to the file at this point. The complete karma.conf.js file is shown here for reference:
/* chapter10/karma.conf.js */
module
.
exports
=
function
(
config
)
{
config
.
set
({
basePath
:
'../'
,
files
:
[
"public_html/js/libs/angular.min.js"
,
"public_html/js/libs/angular-mocks.js"
,
"public_html/js/libs/angular-route.min.js"
,
"public_html/js/libs/angular-resource.min.js"
,
"public_html/js/libs/angular-cookies.min.js"
,
"public_html/js/*.js"
,
"public_html/partials/*.html"
,
"test/**/*Spec.js"
],
preprocessors
:
{
'public_html/partials/*.html'
:
[
'ng-html2js'
]
},
exclude
:
[
],
autoWatch
:
true
,
frameworks
:
[
"jasmine"
],
browsers
:
[
"Chrome"
,
"Firefox"
],
plugins
:
[
"karma-junit-reporter"
,
"karma-chrome-launcher"
,
"karma-firefox-launcher"
,
"karma-jasmine"
,
"karma-ng-html2js-preprocessor"
],
ngHtml2JsPreprocessor
:
{
stripPrefix
:
'public_html/'
});
};
We need to add unit test specifications for the new Login
service and the two new controllers. The following code shows the new test specification for the Login
service. The service relies on a REST service, so we will only test to make sure we can inject the service. We will actually test the service interaction with the REST service during end-to-end testing. If there are any issues, we will find them there. Add this test specification to the project’s servicesSpec.js file:
/* chapter10/servicesSpec.js excerpt */
describe
(
'test Login'
,
function
()
{
var
$rootScope
;
var
login
;
beforeEach
(
module
(
'blogServices'
));
beforeEach
(
inject
(
function
(
$injector
)
{
$rootScope
=
$injector
.
get
(
'$rootScope'
);
login
=
$injector
.
get
(
'Login'
);
}));
it
(
'should test Login service'
,
function
()
{
expect
(
login
).
toBeDefined
();
});
});
The complete servicesSpec.js file is shown here:
/* chapter10/servicesSpec.js complete file */
describe
(
'AngularJS Blog Service Testing'
,
function
()
{
describe
(
'test BlogList'
,
function
()
{
var
$rootScope
;
var
blogList
;
beforeEach
(
module
(
'blogServices'
));
beforeEach
(
inject
(
function
(
$injector
)
{
$rootScope
=
$injector
.
get
(
'$rootScope'
);
blogList
=
$injector
.
get
(
'BlogList'
);
}));
it
(
'should test BlogList service'
,
function
()
{
expect
(
blogList
).
toBeDefined
();
});
});
describe
(
'test BlogPost'
,
function
()
{
var
$rootScope
;
var
blogPost
;
beforeEach
(
module
(
'blogServices'
));
beforeEach
(
inject
(
function
(
$injector
)
{
$rootScope
=
$injector
.
get
(
'$rootScope'
);
blogPost
=
$injector
.
get
(
'BlogPost'
);
}));
it
(
'should test BlogPost service'
,
function
()
{
expect
(
blogPost
).
toBeDefined
();
});
});
describe
(
'test Login'
,
function
()
{
var
$rootScope
;
var
login
;
beforeEach
(
module
(
'blogServices'
));
beforeEach
(
inject
(
function
(
$injector
)
{
$rootScope
=
$injector
.
get
(
'$rootScope'
);
login
=
$injector
.
get
(
'Login'
);
}));
it
(
'should test Login service'
,
function
()
{
expect
(
login
).
toBeDefined
();
});
});
});
Now we need test specifications for the two new controllers. First we show the test specification for the LoginCtrl
controller. We first get a reference to the controller and then call the submit
method attached to the scope. We use a scope property to validate that the method call was successful:
/* chapter10/controllerSpec.js excerpt */
describe
(
'LoginCtrl'
,
function
()
{
var
scope
,
ctrl
;
beforeEach
(
inject
(
function
(
$rootScope
,
$controller
)
{
scope
=
$rootScope
.
$new
();
ctrl
=
$controller
(
'LoginCtrl'
,
{
$scope
:
scope
});
scope
.
submit
();
}));
it
(
'should show submit success'
,
function
()
{
console
.
log
(
"LoginCtrl:"
+
scope
.
sub
);
expect
(
scope
.
sub
).
toEqual
(
true
);
});
});
Next is the test specification for the LogoutCtrl
controller. In this case, we just validate that we can get a reference to the controller. We will validate that the controller actually handles logout correctly when we do end-to-end testing:
/* chapter10/controllerSpec.js excerpt */
describe
(
'LogoutCtrl'
,
function
()
{
var
scope
,
ctrl
;
beforeEach
(
inject
(
function
(
$rootScope
,
$controller
)
{
scope
=
$rootScope
.
$new
();
ctrl
=
$controller
(
'LogoutCtrl'
,
{
$scope
:
scope
});
}));
it
(
'should create LogoutCtrl controller'
,
function
()
{
console
.
log
(
"LogoutCtrl:"
+
ctrl
);
expect
(
ctrl
).
toBeDefined
();
//expect(scope.blogList).toBeUndefined();
});
});
The complete controllerSpec.js file is shown next. Make the changes to your file in the blog application and validate that it matches the version shown here:
/* chapter10/controllerSpec.js complete file */
describe
(
'AngularJS Blog Application'
,
function
()
{
beforeEach
(
module
(
'blogApp'
));
//beforeEach(module('blogServices'));
describe
(
'BlogCtrl'
,
function
()
{
var
scope
,
ctrl
;
beforeEach
(
inject
(
function
(
$rootScope
,
$controller
)
{
scope
=
$rootScope
.
$new
();
ctrl
=
$controller
(
'BlogCtrl'
,
{
$scope
:
scope
});
}));
it
(
'should create show blog entry count'
,
function
()
{
console
.
log
(
"blogList:"
+
scope
.
blogList
);
expect
(
scope
.
blogList
.
length
).
toEqual
(
0
);
//expect(scope.blogList).toBeUndefined();
});
});
describe
(
'BlogViewCtrl'
,
function
()
{
var
scope
,
ctrl
,
$httpBackend
;
beforeEach
(
inject
(
function
(
_$httpBackend_
,
$routeParams
,
$rootScope
,
$controller
)
{
$httpBackend
=
_$httpBackend_
;
$httpBackend
.
expectGET
(
'blogPost'
).
respond
({
_id
:
'1'
});
$routeParams
.
id
=
'1'
;
scope
=
$rootScope
.
$new
();
ctrl
=
$controller
(
'BlogViewCtrl'
,
{
$scope
:
scope
});
}));
it
(
'should show blog entry id'
,
function
()
{
//expect(scope.blogEntry._id).toEqual(1);
//expect(scope.blogList).toBeUndefined();
expect
(
scope
.
blg
).
toEqual
(
1
);
});
});
describe
(
'LoginCtrl'
,
function
()
{
var
scope
,
ctrl
;
beforeEach
(
inject
(
function
(
$rootScope
,
$controller
)
{
scope
=
$rootScope
.
$new
();
ctrl
=
$controller
(
'LoginCtrl'
,
{
$scope
:
scope
});
scope
.
submit
();
}));
it
(
'should show submit success'
,
function
()
{
console
.
log
(
"LoginCtrl:"
+
scope
.
sub
);
expect
(
scope
.
sub
).
toEqual
(
true
);
//expect(scope.blogList).toBeUndefined();
});
});
describe
(
'LogoutCtrl'
,
function
()
{
var
scope
,
ctrl
;
beforeEach
(
inject
(
function
(
$rootScope
,
$controller
)
{
scope
=
$rootScope
.
$new
();
ctrl
=
$controller
(
'LogoutCtrl'
,
{
$scope
:
scope
});
}));
it
(
'should create LogoutCtrl controller'
,
function
()
{
console
.
log
(
"LogoutCtrl:"
+
ctrl
);
expect
(
ctrl
).
toBeDefined
();
//expect(scope.blogList).toBeUndefined();
});
});
});
The test specifications just added will test the new service and the two new controllers. We will also test all the existing controllers, the existing services, and the existing directive when Karma runs.
Right-click the project and select “Test” from the menu. Karma will start. You should see both Chrome and Firefox browser windows open. The NetBeans test results window should open and display a total of 26 passed test cases.
If you get any error messages or failed tests, go back over this section and verify that you completed all the configurations and installations. You can also download the Chapter 10 code from the GitHub project site.
We will make several changes to the end-to-end test specifications for our blog application here. We will need to log into the blog application with the script. Then, once logged in, we will navigate through the blog as before to verify that all previous E2E functionality still works. We will then need to log out with the test script to test the logout functionality.
We already created a Protractor configuration file for the blog application in Chapter 5. The Protractor configuration file is shown here for reference:
/* chapter10/conf.js Protractor configuration file */
exports
.
config
=
{
seleniumAddress
:
'http://localhost:4444/wd/hub'
,
specs
:
[
'e2e/blog-spec.js'
]
};
The blog-spec.js file shown here contains several changes. First notice that the script needs to complete the login form by populating the username and password fields. Then it looks up the login form button by the CSS class name, and clicks the button:
/* chapter10/blog-spec.js Protractor test specification */
describe
(
"Blog Application Test"
,
function
(){
it
(
"should test the main blog page"
,
function
(){
browser
.
get
(
"http://localhost:8383/AngularJsBlog/"
);
//logs into the blog application
element
(
by
.
model
(
"username"
)).
sendKeys
(
"node"
);
element
(
by
.
model
(
"password"
)).
sendKeys
(
"password"
);
element
(
by
.
css
(
'.form-button'
)).
click
();
expect
(
browser
.
getTitle
()).
toEqual
(
"AngularJS Blog"
);
//gets the blog list
var
blogList
=
element
.
all
(
by
.
repeater
(
'blogPost in blogList'
));
//tests the size of the blogList
expect
(
blogList
.
count
()).
toEqual
(
1
);
browser
.
get
(
"http://localhost:8383/AngularJsBlog/#!/
blogPost/5394e59c4f50850000e6b7ea"
);
expect
(
browser
.
getTitle
()).
toEqual
(
"AngularJS Blog"
);
//gets the comment list
var
commentList
=
element
.
all
(
by
.
repeater
(
'comment in blogEntry.comments'
));
//checks the size of the commentList
expect
(
commentList
.
count
()).
toEqual
(
2
);
element
(
by
.
css
(
'.navbar-brand'
)).
click
();
//logs out of the blog application
element
(
by
.
id
(
'lo'
)).
click
();
expect
(
browser
.
getTitle
()).
toEqual
(
"AngularJS Blog"
);
});
});
Once the script has successfully logged into the application, it navigates through the application as before. Then, at the end of the test script, it looks up the logout link by id
. It then clicks the link, logging out of the application.
The end-to-end test specification validates that the login process works. It also validates all the previous functionality tested in Chapter 9. Then it validates that the logout process works correctly.
Now, with those changes added, we are ready to start the end-to-end testing.
Start a new command window and enter the following command to start the test server:
webdriver-manager start
Open a new command window and navigate to the root of the Chapter 10 project. Type the command:
protractor test/conf.js
You should see a browser window open. You should then see the test script log into the blog application and navigate through the pages of the application, and finally log out of the application. When the Protractor script has finished, the browser window will close.
You should see results like the following in the command window when the Protractor script completes. The number of seconds that it takes the script to finish will vary depending on your particular system:
Finished in 3.285 seconds 1 test, 5 assertions, 0 failures
I want to emphasize one last thing about implementing security in a JavaScript application. Any security that you implement in JavaScript can be circumvented by the user, as I explained at the start of the chapter. The login screen and security that we implemented in this chapter are completely dependent on the login REST service.
The login screen is used just as a way to gather and store the user’s credentials in a safe place temporarily and to control the authentication process for each REST service that contains private data. The user’s credentials are removed after each session and have to be entered again at each login, unless the user chooses to save their credentials.
In the next chapter you will see how the user’s credentials are used to gain access to private REST services that add new blog posts and comments. You will first deploy the REST services and the AngularJS application together in a MEAN stack deployment to your local machine to see the whole process in action. Once the application is up and running on your local machine, you will be able to use the developer tools in Chrome to view the REST service logs at runtime: you’ll be able to view the URL, request, and response of each service call.
You will also see any errors that occur. Once you have tested the MEAN stack on your local machine, you will deploy the project to the cloud using Git, which is a distributed version control and source code management (SCM) system initially developed by Linus Torvalds.