I am facing difficulty while authenticating users using token mechanism from Angular JS SPA Page to WEB API.
I do not want to use ASP.NET Identity default tables to add or authenticate user. I have my own database and a table called "EmployeeAccess" table which contains EmployeeNumber as User Id and Password. I want to authenticate the users against the values in this table and then want to grant token so that they gets authorized for subsequent call. I have used all required OWIN and ASP.NET References to achieve the result.
Please see the relevant code below:-
Startup.cs
[assembly: OwinStartup(typeof(Application.WebAPI.Startup))]
namespace Application.WebAPI
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
var myProvider = new AuthorizationServerProvider();
OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = myProvider
};
app.UseOAuthAuthorizationServer(options);
}
}
}
AuthorizationServerProvider.cs
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated(); //
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
string userId = context.UserName;
string password = context.Password;
EmployeeAccessBLL chkEmpAccessBLL = new EmployeeAccessBLL();
EmployeeAccessViewModel vmEmployeeAccess = chkEmpAccessBLL.CheckEmployeeAccess(Convert.ToInt32(userId), password);
if(vmEmployeeAccess != null)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("username", vmEmployeeAccess.EmpName));
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "Provided username and password is incorrect");
return;
}
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.Formatters.Remove(config.Formatters.XmlFormatter);
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
}
}
Login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js"></script>
<script src="../Scripts/AngularControllers/LoginController.js"></script>
<script src="../Scripts/AngularServices/ApiCallService.js"></script>
</head>
<body ng-app="appHome">
<div ng-controller="ctrlLogin">
<label>Employee Number</label>
<input type="text" id="txtEmpNumber" ng-model="md_empnumber" />
<br/>
<br/>
<label>Password</label>
<input type="text" id="txtEmpNumber" ng-model="md_password" />
<button id="btnAdd" type="submit" ng-click="Login()">Login</button>
</div>
</body>
</html>
LoginController.js
var myApp = angular.module('appHome', []);
myApp.controller("ctrlLogin", ['$scope', 'MetadataOrgFactory', '$location', function ($scope, MetadataOrgFactory, $location) {
$scope.Login = function () {
var objLogin = {
'username' : $scope.md_empnumber,
'password' : $scope.md_password,
'grant_type' : 'password'
};
MetadataOrgFactory.postLoginCall('Token', objLogin, function (dataSuccess) {
alert("Welcome " + dataSuccess);
}, function (dataError) {
});
}
}]);
ApiCallService.js
var appService = angular.module('appHome');
appService.factory('MetadataOrgFactory', ['$http', function ($http) {
var url = 'http://localhost:60544';
var dataFactory = {};
dataFactory.postLoginCall = function (controllerName, objData, callbackSuccess, callbackError) {
$http.post(url + '/' + controllerName, objData).then
(function success(response) {
alert("Success");
callbackSuccess(response.data);
}, function error(response) {
callbackError(response.status);
});
};
return dataFactory;
}])
I am getting the below two errors when click on Login Button:-
OPTIONS http://localhost:60544/Token 404 (Not Found)
XMLHttpRequest cannot load http://localhost:60544/Token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:60512' is therefore not allowed access. The response had HTTP status code 404.
I searched many posts but none of the post is meeting my criteria so thought of posting my query. Please help me on that.
The error is the result of the OPTIONS call.
The HTTP OPTIONS method is used to describe the communication options for the target resource. The client can specify a specific URL for the OPTIONS method, or an asterisk (*) to refer to the entire server.
Since you do not handle these, you'll see 404 (not found). You can read about CORS here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
One way to implement this is to add in Global.asax the following code:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
protected void Application_BeginRequest()
{
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
// Cache the options request.
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", HttpContext.Current.Request.Headers["Origin"]);
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, PUT, DELETE, POST, OPTIONS");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
HttpContext.Current.Response.End();
}
}
}