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';
var id = 'app';
var app = angular.module('app', [
]);
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
$httpProvider.defaults.transformRequest = [function (data) {
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;
}];
}]);
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) {
var vm = this;
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) {
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) {
accessToken = data.access_token;
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;
}
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.