Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using Web API 2 (Individual User Account + CORS Enabled) from AngularJS Client

0.00/5 (No votes)
11 Aug 2014 3  
How to use ASP.NET Web API 2 with Individual User Account from AngularJS client with CORS enabled to issue a Bearer token.

Final project running...

Edit:

I've upgraded the code to use VS 2013 update 3 new templates and "Microsoft.AspNet.Identity.Core" v2.1 dll, which has a lot of new features and enhancements to v1 like the flexibility to change the Id of the user table from string to an int for example which what I've done and you can find in the project code "WebApiAngularJs(Identity.Core v2).zip"

Introduction

This tutorial will show how we can use AngularJS client to consume a CORS enabled Web API 2 with individual user account authentication mode and request a data from an API Controller that uses the Authorize attribute.

The main problems that will face us to achieve this will be:

  • How to configure the Angular $http service to serialize the request data to be url-encoded.
  • How to enable the Cross Origin Resource Sharing (CORS) on the server side to allow requests from different domain (Origins).

I will go through the project setup step by step, or you can just skip this and download the source code.

This tutorial contains the following sections:

 

Prerequisites

 

Create the Web API Project

I will use the new VS 2013 Web Api Template to bootstrap our project.

Start Visual Studio 2013. From the File menu, click New Project. In the New Project dialog, click Web in the left pane and ASP.NET Web Application in the middle pane. Enter WebApi2Service for the project name and click OK.

In the New ASP.NET Project dialog, select the "Web API" template.

Click Change Authentication. In the Change Authentication dialog, select Individual User Accounts. Click OK.

 

Click OK in the New ASP.NET Project dialog to create the project.

 

At this stage, if we tried to run the application and request the data from /api/values controller we will get the error Authorization has been denied for this request, and that's because the ValuesController is decorated by Authorize attribute, which forces the user of that controller to be authenticated.

So the next step is to create a web client that will register/login a user and request the data from the ValuesController.

 

Create the AngularJS Client Application

Our AngularJS Client project UI will be very basic and will use the minimum to achieve the following:

  • Register a new user.
  • Login and authenticate a registered user and retrieve a bearer token.
  • Use the issued token to request a data from a secured web api controller that requires the user to be authenticated.

We will create a separate project to come across the CORS problem, and we will see how we can solve it.

In Solution Explorer, right-click the solution. Select Add and then select New Project.

In the Add New Project dialog, select ASP.NET Web Application, as before. Name the project AngularJsClient. Click OK.

In the New ASP.NET Project dialog. Click OK.

Now we need to install AngularJs package, so in Visual Studio, from the Tools menu, select Library Package Manager, then select Package Manager Console. In the Package Manager Console window, type the following command:

Install-Package AngularJs.Core -ProjectName AngularJsClient

In Solution Explorer, right-click the AngularJsClient project. Select Add and then select JavaScript file.

In the Specify name for Item dialog, Name the file app.js. Click OK.

The first important thing that we need to consider is that the new Web API 2 requires the request data to be URL encoded, which means that we have to set the content-type header to "application/x-www-form-urlencoded;charset=utf-8", and also we need to serialize the data that we sent to allow the server to receive the data correctly, so we will configure the $httpProvider service post method to do that, so our app.js file will be as follows :

(function () {
    'use strict';
    // Module name is handy for logging
    var id = 'app';
    // Create the module and define its dependencies.
    var app = angular.module('app', [
    ]);
    app.config(['$httpProvider', function ($httpProvider) {
        // Use x-www-form-urlencoded Content-Type
        $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
        // Override $http service's default transformRequest
        $httpProvider.defaults.transformRequest = [function (data) {
            /**
             * The workhorse; converts an object to x-www-form-urlencoded serialization.
             * @param {Object} obj
             * @return {String}
             */
            var param = function (obj) {
                var query = '';
                var name, value, fullSubName, subName, subValue, innerObj, i;
                for (name in obj) {
                    value = obj[name];
                    if (value instanceof Array) {
                        for (i = 0; i < value.length; ++i) {
                            subValue = value[i];
                            fullSubName = name + '[' + i + ']';
                            innerObj = {};
                            innerObj[fullSubName] = subValue;
                            query += param(innerObj) + '&';
                        }
                    }
                    else if (value instanceof Object) {
                        for (subName in value) {
                            subValue = value[subName];
                            fullSubName = name + '[' + subName + ']';
                            innerObj = {};
                            innerObj[fullSubName] = subValue;
                            query += param(innerObj) + '&';
                        }
                    }
                    else if (value !== undefined && value !== null) {
                        query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
                    }
                }
                return query.length ? query.substr(0, query.length - 1) : query;
            };
            return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
        }];
    }]);
    // Execute bootstrapping code and any dependencies.
    app.run(['$q', '$rootScope',
        function ($q, $rootScope) {
        }]);
})();

You can see make-angularjs-http-service-behave-like-jquery-ajax for more information about angularjs $http.post vs Ajax post method.

Repeat the previous step to add another 2 JavaScript files named mainCtrl.js and userAccountService.js

The first file mainCtrl.js will be the controller code that we will use for our view (web page), and the code will be like this:

(function () {
    'use strict';
    var controllerId = 'mainCtrl';
    angular.module('app').controller(controllerId,
        ['userAccountService', mainCtrl]);
    function mainCtrl(userAccountService) {
        // Using 'Controller As' syntax, so we assign this to the vm variable (for viewmodel).
        var vm = this;
        // Bindable properties and functions are placed on vm.
        vm.title = 'mainCtrl';
        vm.isRegistered = false;
        vm.isLoggedIn = false;
        vm.userData = {
            userName: "",
            password: "",
            confirmPassword : "",
        };
        vm.registerUser = registerUser;
        vm.loginUser = loginUser;
        vm.getValues = getValues;
        function registerUser() {
            userAccountService.registerUser(vm.userData).then(function (data) {
                vm.isRegistered = true;
            }, function (error, status) {
                vm.isRegistered = false;
                console.log(status);
            });
        }
        function loginUser() {
            userAccountService.loginUser(vm.userData).then(function (data) {
                vm.isLoggedIn = true;
                vm.userName = data.userName;
                vm.bearerToken = data.access_token;
            }, function (error, status) {
                vm.isLoggedIn = false;
                console.log(status);
            });
        }
        function getValues() {
            userAccountService.getValues().then(function (data) {
                vm.values = data;
                console.log('back... with success');
            });
        }
    }
})();

For the file userAccountService.js, it will be our service that will contain methods to call the server Web API to register and login a user, and also a method to get the values from the ValuesController after the login and authorization, so the code will be like this:

(function () {
    'use strict';
    var serviceId = 'userAccountService';
    angular.module('app').factory(serviceId, ['$http', '$q', userAccountService]);
    function userAccountService($http, $q) {
        // Define the functions and properties to reveal.
        var service = {
            registerUser: registerUser,
            loginUser: loginUser,
            getValues: getValues,
        };
        var serverBaseUrl = "http://localhost:60737";
        
        return service;
        var accessToken = "";
        function registerUser(userData) {
            var accountUrl = serverBaseUrl + "/api/Account/Register";
            var deferred = $q.defer();
            $http({
                method: 'POST',
                url: accountUrl,
                data: userData,
            }).success(function (data, status, headers, cfg) {
                console.log(data);
                deferred.resolve(data);
            }).error(function (err, status) {
                console.log(err);
                deferred.reject(status);
            });
            return deferred.promise;
        }
        function loginUser(userData) {
            var tokenUrl = serverBaseUrl + "/Token";
            if (!userData.grant_type) {
                userData.grant_type = "password";
            }
            var deferred = $q.defer();
            $http({
                method: 'POST',
                url: tokenUrl,
                data: userData,
            }).success(function (data, status, headers, cfg) {
                // save the access_token as this is required for each API call. 
                accessToken = data.access_token;
                // check the log screen to know currently back from the server when a user log in successfully.
                console.log(data);
                deferred.resolve(data);
            }).error(function (err, status) {
                console.log(err);
                deferred.reject(status);
            });
            return deferred.promise;
        }
        function getValues() {
            var url = serverBaseUrl +  "/api/values/";
            var deferred = $q.defer();
            $http({
                method: 'GET',
                url: url,
                headers: getHeaders(),
            }).success(function (data, status, headers, cfg) {
                console.log(data);
                deferred.resolve(data);
            }).error(function (err, status) {
                console.log(err);
                deferred.reject(status);
            });
            return deferred.promise;
        }
        // we have to include the Bearer token with each call to the Web API controllers. 
        function getHeaders() {
            if (accessToken) {
                return { "Authorization": "Bearer " + accessToken };
            }
        }
    }
})();

Finally we need to add the html file that will contain the UI page, so as before in Solution Explorer, right-click the AngularJsClient project. Select Add and then select HTML Page.

In the Specify name for Item dialog, Name the file Index.html. Click OK.

As I mentioned our html will be primitive, so something like the this should be enough :

    <xmp>
        <!DOCTYPE HTML>
        <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title></title>
        </head>
        <body>
            <div data-ng-app="app" data-ng-controller="mainCtrl as vm">
                <h3> Test Values Controller</h3>

                <div>
                    <span data-ng-show="vm.isRegistered">User Registered Successfully</span><br />
                    <span data-ng-show="vm.isLoggedIn">Hello : {{vm.userName}}</span><br />
                    <span data-ng-show="vm.isLoggedIn">Your Bearer Token is : {{vm.bearerToken}}</span>
                </div>
                <br />
                <div>
                    <input type="text" name="userName" placeholder="Name" data-ng-model="vm.userData.userName" />
                    <input name="password" placeholder="Password" data-ng-model="vm.userData.password" />
                    <input name="confirmPassword" placeholder="Password" data-ng-model="vm.userData.confirmPassword" />
                    <input type="button" id="registerUser" value="Register" data-ng-click="vm.registerUser()" />
                    <input type="button" id="logniUser" value="Login" data-ng-click="vm.loginUser()" />
                </div>

                <button id="getValues" data-ng-click="vm.getValues()">Get Values</button>

                <ul>
                    <li data-ng-repeat="v in vm.values">
                        <div>
                            <strong>{{v}}</strong>
                        </div>
                    </li>
                </ul>

            </div>
            <script src="Scripts/angular.js"></script>

            <script>

            </script>
            <script src="app.js"></script>
            <script src="mainCtrl.js"></script>
            <script src="userAccountService.js"></script>

        </body>
    </html>
</xmp>

Enable Cross Origin Resource Sharing (CORS) in Web API

By default browser security prevents a web page from making AJAX requests to another domain. This restriction is called the same-origin policy, however, we can allow cross-origin request by enable CORS on our Web API Server.

To enable CORS in Web API, use the Microsoft.AspNet.WebApi.Cors package, which is available on NuGet.

In Visual Studio, from the Tools menu, select Library Package Manager, then select Package Manager Console. In the Package Manager Console window, type the following command:

Install-Package Microsoft.AspNet.WebApi.Cors -ProjectName WebApi2Service

This installs the CORS package, along with any dependencies, into the WebApi2Service project.

In Solution Explorer, expand the WebApi2Service project. Open the file App_Start/WebApiConfig.cs. Add the following code to the WebApiConfig.Register method.

Now run both projects (WebApi2Service and AngularJsClient) and try to register a user by entering a UserName, a password a confirmed password and click on Register button you will notice that we successfully registered the user.


Now try to press the login button and you will be surprised that you will get an error relating to CORS not enabled for /Token !!!.

But we already enabled CORS on the Web API, so what did we miss !!!

The reason for this error basically is that the CORS is enabled for the Web API only, not for the Token middleware Provider that the client requested to issue the token, so to solve this problem we need to add the header "Access-Control-Allow-Origin" when the provider issues the token so :

In Solution Explorer, expand the WebApi2Service project. Open the file Providers/ApplicationOAuthProvider.cs, and add this line of code to ApplicationOAuthProvider.GrantResourceOwnerCredentials method.

Now try again, and login the user that you already registered in the previous time, and the login should succeed and you can see the token that we got from the server after we logged-in.

And finally, click the GetValues button and you also should get the values from the Values Controller successfully.

Summary

In this tutorial we've solved most of the problems that we were facing with using the new Web API 2 beaere token feature from AngularJs client, and I hope it has helped you understand a little bit more about Web API 2 and AngularJs.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here