Bad Request (400) when using Web API Authentication Authentication from Angualr JS

I want to set up web API token authentication using Angular JS as client. I am very new to this concept of Token authentication inside Web API.

I don't want to use the default ASP.NET Identity tables to add or authenticate a user. I have my own database and table called "EmployeeAccess" which contains EmployeeNumber as User Id and Password. I want to authenticate users against the values ​​in this table, and then I want to provide a token so that they get permission to call it later. I have used all the OWIN and ASP.NET references needed to achieve the result. Here is my code for the various components: -

Global.asax

public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
           // AreaRegistration.RegisterAllAreas();
            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();
            }
        }
    }

      

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);
        }
    }

      

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;
            }
        }               
    } 

      

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,{headers:{ 'Content-Type': 'application/x-www-form-urlencoded' }}).then
            (function success(response) {
                alert("Success");
                callbackSuccess(response.data);
            }, function error(response) {
                callbackError(response.status);
            });
    };
    return dataFactory;
}])

      

When I click the "Login" button, I get below the error message: -

POST http: // localhost: 60544 / Token 400 (failed request)

When I debugged the WebAPI code, I found that this "GrantResourceOwnerCredentials ()" method inside "AuthorizationServerProvider.cs" never runs. An error message appears before this. Only the "ValidateClientAuthentication" and "MatchEndpoint" methods are executed.

Please help me to run the Token Authentication script successfully. If any code turns out to be redundant, please let me know so I can remove it.

+3


source to share


1 answer


Ok this is going to be a long answer, but hang on to the end :)

Step 1: remove Global.asax

Global.asax is not needed when you run the Owin pipeline. Startup.cs is what I would say Owins Global.asax. They basically fill the same target, so go ahead and delete it.

Step 2: Remove Cors Handling in WebApiConfig.cs

This code is not needed because you are already declaring it in Startup.cs.

app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

      

Now your WebApiConfig.cs will look like this

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);
    }
}

      

Step 3: Add Web Api and Token Authenticator to Owin Pipeline in Startup.cs

Instead of binding the WebApiConfig in Global.asax, you attach it to the pipeline. Also apply the bearer token handling to the pipeline.

Your Startup.cs will now look like this:



public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/Token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new AuthorizationServerProvider()
        };
        app.UseOAuthAuthorizationServer(options);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

        //Register the web api to the pipeline 
        HttpConfiguration config = new HttpConfiguration();
        WebApiConfig.Register(config);
        app.UseWebApi(config);
    }
}

      

Step 4: add headers to the request in AuthorizationServerProvider.cs

public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated(); 
    }
    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        SetContextHeaders(context);
        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;
        }
    }
    private void SetContextHeaders(IOwinContext context)
    {
        context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
        context.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "GET, PUT, DELETE, POST, OPTIONS" });
        context.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Content-Type, Accept, Authorization" });
        context.Response.Headers.Add("Access-Control-Max-Age", new[] { "1728000" });
    }
} 

      

Step 5: Make the correct request to the Oauth server

The oauth server request must be of the x-www-form-urlencoded content type, which is basically a string. I also added a promise instead of callbacks, which is what $ q does. IMO I think the promise is more clear to read

TIP: Don't send credentials in clear text. You can endecode them to Base64 string with btoa (password) and then decode it in your backend.

angular.module('appHome').factory('MetadataOrgFactory', ['$http', function ($http) {

    var url = 'http://localhost:60544';    
    var dataFactory = {};  
    dataFactory.login = function (userName, password) {
        var deferred = $q.defer();

        $http({
            method: 'POST',
            url: url + '/Token',
            processData: false,
            contentType: 'application/x-www-form-urlencoded',
            data: "grant_type=password&username=" + userName + "&password=" + password,
        }).
            success(function (data) {
                deferred.resolve(data);
            }).
            error(function (message, status) {              
                console.log(message);
                deferred.reject(message, status);
            });

        return deferred.promise;
    };  
    return dataFactory;
}]);

      

Step 6: make a login request from your controller

angular.module('appHome', []);
angular.module('appHome').controller("ctrlLogin", ['$scope', 'MetadataOrgFactory', '$location', function ($scope, MetadataOrgFactory, $location) {
    $scope.Login = function () {

        MetadataOrgFactory.postLoginCall($scope.md_empnumber, $scope.md_password).then(
            function (result) {
                //success
            },
                function (error, statusCode) {
                    console.log(error);
                }
            );;
    }
}]);

      

What is it.

+9


source







All Articles