Introduction
In today’s design methodologies, security is something that is not asked for but expected! Often a TFS User Story with Tasks cumulating to 100+ points in your backlog!
With newer technologies, it can be a daunting process to lock down security correctly, but one way is to provide a proof-of-concept demo, detailing how your system will restrict users to particular Web API methods or the whole Web API service.
Application security comes in two flavours:
- Intranet application (AD or LDAP)
- Internet application (Identity & Bearer Tokens).
This blog will concentrate on option 1, intranet security with Windows Active Directory. I will detail how to secure your application using Bearer Tokens in my next blog.
Technologies Used
- Visual Studio 2013
- Web API
- Angular.js (
PM> Install-Package angularjs
) - Bootstrap CSS (
PM> Install-Package bootstrap -Pre
) - Angular BlockUI (
PM> Install-Package angular-block-ui
) - Angular UI-Router (
PM> Install-Package Angular.UI.UI-Router
)
- Local IIS (Express)
- Express LocalDB
- Entity Framework 6 (
PM> Install-Package EntityFramework)
- Windows 7 - Computer Management Groups
- Soap-UI, Fiddler, Post-Man (test REST services)
In this blog, I want to keep things simple, by using a (SQL) LocalDB and Local IIS Express as security can become bloated with multiple plug-ins or third party controls. This blog will deal with restricting users to a particular service method.
Project Structure
The solution is split up into two projects, a client (MVC) AngularJS project and a server (MVC) Web API project. As you can see the Web API project hosts the LocalDB, which Entity Framework interacts with. The client web project is a standard AngularJS application, with modules, controllers, services, routing and multiple views.
From the properties of the Web API project, you will see that the server is hosted using IIS Express.
The solution will start both the Web API project and the Web Client project together (Web API first).
Server Configuration Prior to Development
Enable Web API Windows Security
Within the Web API web.config file, ensure the following authentication element is present.
<system.web>
<authentication mode="Windows" />
</system.web>
Computer Management Group
Add a new Group (WebSiteUser), which will be the Role associated with a particular user. Do not add yourself to this group yet, as we will initially test to see that you are restricted form calling the Role restricted service method.
Microsoft Internet Explorer
In IE you can check the setting with Tools > Internet Options > Advanced and look for a setting Enable Windows Integrated Authentication. When you go to the tab Security and then Intranet and Custom Level, then you will find a setting at the bottom to specify if IE should logon automatically or prompt for the username and password.
Google Chrome
Chrome uses the same IE settings.
Mozilla Firefox
Firefox automatically supports Windows Integrated Authentication but by default it will prompt for the username and password. If you want to change this, then you have to go the the settings of FireFox. So type about:config in the address bar and look for network.automatic-ntlm-auth.trusted-uris and enter the url of your intranet server. You can add multiple sites by comma delimiting them.
IIS Express & IIS
Maybe it is still not working and returning a 401 Unauthorized message. When using IIS Express you have to change a configuration setting. Look for the ApplicationHost.config in your “My Documents\IISExpress\config” folder. There you have to add:
<windowsAuthentication enabled="true">
When you will host you application in IIS 7 or higher, then you have to do the same. You can also modify this in the ApplicationHost.config file which can be found in the “system32\inetsrv” folder. But it is easier to change it in the Internet Information Services Manager. Go to Authentication and enable the Windows Authentication module.
Server Side Code Explanation
Securing Web API Method Authorization
With this Web API method, we are only letting valid active directory users access it, by adding the [Authorize] annotation to the method.
[Authorize]
[HttpGet]
[Route("api/People")]
public IQueryable<Person> GetPeople()
{
return db.People;
}
Securing Web API Method by Group
We want to secure the Web API method by only letting authorized (active directory) users and users that are within the group\role WebSiteUser.
[Authorize]
[Authorize(Roles = "WebSiteUser")]
[HttpGet]
[Route("api/AandroidConversions")]
public IQueryable<AandroidConversion> GetAandroidConversions()
{
return db.AandroidConversions;
}
Enabling CORS (Cross Origin Request Sharing)
Since our web client will be using a different port number to the Web API, we need to enable CORS within the Web API project. This can be done by updating the Global.asax class method Application_BeginRequest to include the following code:
Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
Update the method Register within the class WebApiConfig
var cors = new EnableCorsAttribute("*", "*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
Client Side Code Explanation
Index Html Page
The index page is very basic Html & AngularJS, with custom AngularJS code (module, controller & service). Two hyperlinks that load separate views and call the appropriate Controller method. A <Div> statement to inject the views into. The view basically has two links, one will call an authorized enabled service method and the other will call an authorized role enabled service method.
<!DOCTYPE html>
<html ng-app="app">
<head>
<title>Angular + WebAPI AD Security</title>
<!--
<link href="Content/bootstrap/bootstrap.css" rel="stylesheet" />
<link href="Content/angular-block-ui.css" rel="stylesheet" />
<!--
<script src="Scripts/angular.js"></script>
<script src="Scripts/angular-ui-router.js"></script>
<script src="Scripts/angular-block-ui.min.js"></script>
<!--
<script src="App/app.js"></script>
<script src="App/services.js"></script>
<script src="App/controllers.js"></script>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<ul class="nav navbar-nav">
<li ng-controller="customerController">
<a href="#/Authorised" title="'Authorised' only windows request" ng-click="getAllACustomerValues()">
Authorised Request
</a>
</li>
<li ng-controller="androidController">
<a href="#/Restricted" title="'Authorised' and 'User Group' request" ng-click="getAllAndroidValues()">
Restricted (Authorised) Request
</a>
</li>
</ul>
</div>
</nav>
<!--
<div class="container">
<div id="ui-view" ui-view></div>
</div>
</body>
</html>
Angularjs App
There is nothing special about the App module or it’s routing;
angular.module('app',
[
'ui.router',
'app.services',
'app.controllers',
'blockUI',
]
)
.value("baseWebApiUrl", "http://localhost:57158/")
.config(['$stateProvider',
function ($stateProvider) {
$stateProvider
.state('Authorised', {
url: '/Authorised',
templateUrl: './Views/Authorised.html',
controller: 'customerController'
})
.state('Restricted', {
url: '/Restricted',
templateUrl: './Views/Restricted.html',
controller: 'androidController'
});
}]);
Angularjs Controller
Again nothing on-to-ward happing within the controllers;
.controller('androidController', function ($scope, $http, $log, blockUI, androidService) {
$log.debug('Enter androidController');
$scope.getAllAndroidValues = function () {
$log.debug('Enter getAllAndroidValues');
blockUI.start();
androidService.getAllAndroidData()
.then(function (results) {
$scope.$parent.sunbursts = results.data
},
function (results) {
alert("Failed to make android data request.");
});
blockUI.stop();
$log.debug('Exit getAllAndroidValues');
}
$log.debug('Exit androidController');
})
AngularJS Service
But in the service we tie up the Windows credentials that the server side is expecting and will authenticate against.
.service('androidService', ['$http', function ($http) {
this.getAllAndroidData = function () {
return $http({
method: 'GET',
withCredentials: true,
data: 'json',
url: 'http://localhost:57158/api/AandroidConversions'
});
};
}])
AngularJS Views
The views are expected small snippets of html code with angular expression binding;
<div class="row">
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="person in people">
<td>
{{person.id}}
</td>
<td>
{{person.first_name}}
</td>
<td>
{{person.last_name}}
</td>
</tr>
</tbody>
</table>
</div>
Calling Authorised Service
By clicking on the Authorised Request link, you will initiate a request call to the Web API service method, which challanages the client to declare who they are, thus you wiill initially get prompted. Once you have entered valid window credentials , an AngularJS Ajax spinner will be displayed, leading to a simple grid displaying informatio.
If you now click again, your token (session) was cached and you will not be prompted for your credentials.
Authorized and passing data back to AngularJS client.
You’re Active Directory Group WebSiteUser has no entries at this stage, so if you click on the Restricted (Authorised) Request you will again be prompted for your credentials, but when you enter them and click OK, you will keep getting prompted because you are not a member of the WebSiteUser group. In fact nobody will be able to access the Web API method because nobody is a member of this AD group!
Windows security will not let you access the Web API method;
Valid Authorized & Role
Add yourself to the Active Directory Group WebSiteUser.
Click on the Restricted (Authorised) Request link, the server will challange the client, but this time your credentials are valid for the Web API method.
Conclusion
Configuring AngularJS and IIS for Active Directory security is straight forward; you just need to know what has to be configured. Remember this is authentication (given access) not authorisation (who are you) which is where certificates come in.
In my next blog I will detail how to use AngularJS with the WWW security, basically incorporating OWIN & Identity (Bearer Tokens).