65.9K
CodeProject is changing. Read more.
Home

AngularJS & Web API Active Directory Security

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (22 votes)

Aug 28, 2015

CPOL

6 min read

viewsIcon

94083

downloadIcon

2084

AngularJS and Web API Active Directory Security (Authorisation)

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:

  1. Intranet application (AD or LDAP)
  2. 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

  1. Visual Studio 2013
    1. Web API
    2. Angular.js (PM> Install-Package angularjs)
    3. Bootstrap CSS (PM> Install-Package bootstrap -Pre)
    4. Angular BlockUI (PM> Install-Package angular-block-ui)
    5. Angular UI-Router (PM> Install-Package Angular.UI.UI-Router)
  2. Local IIS (Express)
  3. Express LocalDB
  4. Entity Framework 6 (PM> Install-Package EntityFramework)
  5. Windows 7 - Computer Management Groups
  6. 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

// Web API configuration and services
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>

    <!--Styles-->
    <link href="Content/bootstrap/bootstrap.css" rel="stylesheet" />
    <link href="Content/angular-block-ui.css" rel="stylesheet" />

    <!--AngularJS & Plugins-->
    <script src="Scripts/angular.js"></script>
    <script src="Scripts/angular-ui-router.js"></script>
    <script src="Scripts/angular-block-ui.min.js"></script>

    <!--Custom Angular Scripts-->
    <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>

    <!--inject views-->
    <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',
    [
        // Dependency Inject Goes inside this array
        'ui.router',  // we inject/load ui-router for routing
        'app.services', // we inject/load the services
        'app.controllers', // we inject/load the controllers
        'blockUI', // display spinner when making AJAX calls       
    ]
)
    .value("baseWebApiUrl", "http://localhost:57158/")

    .config(['$stateProvider',
        function ($stateProvider) {
            // our routers, self explanatory
            $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');

        /*================================================================================
        Author      : Bert O'Neill
        Method      : getAllAndroidValues
        Parameters  : N\A
        Date        : 30-Aug-2015
        History     : Initial Draft (30-Aug-2015)
        Description : To use this Web API the caller must be part of a Windows group (WebSiteUser).
                      Call service to pull back all android related data from database.
                      Bind results to parent scope, which in turn is bound to UI control
        Test Prj.   : N\A
        ================================================================================*/

        $scope.getAllAndroidValues = function () {
            $log.debug('Enter getAllAndroidValues');
            blockUI.start(); // block UI
            androidService.getAllAndroidData() // pass back the promise and handle in controller (service is only a pass through\shared logic between ctrls)
                .then(function (results) {                   
                    $scope.$parent.sunbursts = results.data // update the parent scope as you have nested controllers in view                   
                },
                function (results) {
                    alert("Failed to make android data request."); // log error
                });

            blockUI.stop(); // unblock UI
            $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) {

    /*================================================================================
    Author      : Bert O'Neill
    Method      : getAllAndroidData
    Parameters  : N\A
    Date        : 30-Aug-2015
    History     : Initial Draft (30-Aug-2015)
    Description : Public API method with user group restrictions.
                  Call Web API Rest method to pull back all android related data from database.                  
    Test Prj.   : N\A
    ================================================================================*/

      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).