Click here to Skip to main content
15,881,139 members
Articles / Web Development / HTML

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

Rate me:
Please Sign up or sign in to vote.
4.92/5 (37 votes)
11 Aug 2014CPOL6 min read 172.4K   5.3K   68   34
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.

Image 2

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

Image 3

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

Image 4

 

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.

Image 5

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.

Image 6

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.

Image 7

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.

Image 8

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

Image 9

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.

Image 10

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

Image 11

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.

Image 12

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.

Image 13
Image 14

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

Image 15

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.

Image 16

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.

Image 17

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Australia Australia
A Senior software developer with over 14 years of experience, love to write code, and write it with the best practices. I have expertise over different desktop and web technologies.

Comments and Discussions

 
QuestionThis is not working for me! Kindly Guide!! Pin
RupeshBhurke13-Feb-18 23:53
RupeshBhurke13-Feb-18 23:53 
QuestionProviders/ApplicationOAuthProvider.cs Pin
Arkadeep De26-Feb-17 1:13
professionalArkadeep De26-Feb-17 1:13 
QuestionThanks Pin
tonyduckett10-Nov-16 3:35
tonyduckett10-Nov-16 3:35 
Question5 Star Pin
Vishal_Kumar25-Feb-16 1:21
Vishal_Kumar25-Feb-16 1:21 
GeneralNice Article Pin
Alireza_13629-Jan-16 15:36
Alireza_13629-Jan-16 15:36 
Praise5 star Pin
Master-Ram19-Nov-15 23:20
professionalMaster-Ram19-Nov-15 23:20 
BugVS2015 - not working Pin
MiloTheGreat15-Nov-15 4:48
MiloTheGreat15-Nov-15 4:48 
QuestionGrantResourceOwnerCredentials method does not get called even Pin
Abinash Patra24-Sep-15 12:34
Abinash Patra24-Sep-15 12:34 
QuestionAwesome tutorial Pin
hqho2-Dec-14 6:13
hqho2-Dec-14 6:13 
Questioncannot download the new source code Pin
Member 110180957-Nov-14 5:05
Member 110180957-Nov-14 5:05 
AnswerRe: cannot download the new source code Pin
Omar Alani7-Nov-14 20:54
Omar Alani7-Nov-14 20:54 
QuestionHow to do this without Entity Framework but Dapper? Pin
YagizOzturk19-Aug-14 8:19
YagizOzturk19-Aug-14 8:19 
AnswerRe: How to do this without Entity Framework but Dapper? Pin
Omar Alani20-Aug-14 1:25
Omar Alani20-Aug-14 1:25 
QuestionWhy only url encoded? Pin
Ibrahim Islam15-Aug-14 8:30
Ibrahim Islam15-Aug-14 8:30 
AnswerRe: Why only url encoded? Pin
Omar Alani15-Aug-14 21:31
Omar Alani15-Aug-14 21:31 
Question500 (null request in GetOwinContext) on OPTIONS preflight request Pin
Daniele Fusi28-Jun-14 10:38
Daniele Fusi28-Jun-14 10:38 
AnswerRe: 500 (null request in GetOwinContext) on OPTIONS preflight request Pin
Omar Alani19-Aug-14 3:26
Omar Alani19-Aug-14 3:26 
QuestionIs there a way to make this self hosted? Pin
eugenegoldberg13-Jun-14 3:32
eugenegoldberg13-Jun-14 3:32 
AnswerRe: Is there a way to make this self hosted? Pin
Omar Alani19-Aug-14 3:18
Omar Alani19-Aug-14 3:18 
QuestionHow to use this is in visual studio 2012 Pin
Member 1083611122-May-14 19:44
Member 1083611122-May-14 19:44 
AnswerRe: How to use this is in visual studio 2012 Pin
Omar Alani23-May-14 1:30
Omar Alani23-May-14 1:30 
QuestionExcellent Project! Pin
gaurav_sw22-May-14 4:55
gaurav_sw22-May-14 4:55 
QuestionWork perfect but I have a question for redefine with $scope Pin
Member 919473511-May-14 20:59
Member 919473511-May-14 20:59 
AnswerRe: Work perfect but I have a question for redefine with $scope Pin
Omar Alani14-May-14 3:38
Omar Alani14-May-14 3:38 
BugPost not working Pin
Member 1077037124-Apr-14 22:31
Member 1077037124-Apr-14 22:31 
Hi,

I keep getting 401 unauthorized when testing your cade and calling api/values/POST. Seems like permissions are not set correctly.

Kind regards

/Peter

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.