Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET WebApi Answer 400 Bad Request to OPTIONS on ValidateClientAuthentication, even on context.Validated()

I have an angularjs HTML client to a WebApi project. When I test the APIs via POSTMAN or other REST Client, it seems that everything is ok.

When I start using browsers with my angularjs client, browsers always start preflight requests with OPTIONS. There, my WebAPI always answers 400 Bad Request - I am still on a "/api/token" phase.

I have already attached every single point of my WebAPI project to the debugger. I have also changed several points according to several answers here in SO on how to enable CORS. Some of them I have already tried: changing web.config to add headers enabling cors on every request, adding cors to WebApi startup, enabling cors at "/token" overridden functions.

Here is what I got so for:

Angularjs TypeScript call to "/api/token":

logIn = (userName: string, userPassword: string): ng.IPromise<void> => {
    var postData = {
        "grant_type": "password",
        "client_id": this.appConfiguration.ClientId,
        "client_secret": this.appConfiguration.ClientSecret,
        "username": userName,
        "password": userPassword
    };
    return this.$http.post<models.LoggedUserModel>('http://local.web.api/api/token', $.param(postData), {
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    }).then((result) => {
        this.localStorageService.set('Auth', result);
        this.goHome(true);
    }).catch((error) => {
        console.warn(error);
    });
}

Here is the only function that is called on my WebApi:

  public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
  {
      // handle cors requests
      if (!string.IsNullOrEmpty(context.OwinContext.Request.Headers.Get("Origin")))
      {
          context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new string[] { "*" });
      }
      try
      {
          // retrieve clientId and clientSecret from request body
          string clientId;
          string clientSecret;
          if (context.TryGetFormCredentials(out clientId, out clientSecret))
          {
              // here it comes our application specific security code....
          }
          else
          {
              // this is part of enabling CORS..
              if (context.Request.Method.ToUpper() == "OPTIONS")
              {
                  // it returns OK to preflight requests having an empty body
                  context.Validated();
              }
          }
      }
      finally
      {
          // log stuff...
      }
  }

If I just left OWIN Cors stuff, adding headers and calling ´context.Validated()´ it all continues the same. Here is what I get:

Firefox Network Tab:
--------------------
Request URL: http://local.web.api/api/token
Request method: OPTIONS
Remote address: 127.0.0.1:80
Status code: 400 Bad Request
Version: HTTP/1.1

Request headers:
----------------
Host: local.web.api
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.7,pt-BR;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization
Origin: http://local.web.client
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

Response headers:
-----------------
Access-Control-Allow-Origin: *
Cache-Control: no-cache
Content-Length: 34
Content-Type: application/json;charset=UTF-8
Date: Tue, 22 Dec 2015 15:24:23 GMT
Expires: -1
Pragma: no-cache
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET

I would really appreciate some ideas of where to got. This is brand new to me, and I do operate some other WebApi projects + angularjs.

like image 786
rodrigogq Avatar asked Nov 09 '22 23:11

rodrigogq


1 Answers

Ok, that's is terrible but I found the issue. I am using a http interceptor on angularjs that would automatically check for a logged user and add an Authorization header with the Bearer token when needed. Problem is I was doing it wrongly.

I created a new property in the config object, bypassToken as boolean, that would be the flag to add or not the Authorization header. Removing this actually fixed the code. Not sure why, but analyzing the request now I can see that all headers are actually sending as expected: with the Content-Type which was not being filled on the first case correctly. Weird though no warning was thrown by angularjs.

// http auth interceptor
angularApplication.factory('httpAuthInterceptor', ['$rootScope', '$injector', '$location', ($rootScope, $injector, $location): ng.IHttpInterceptor => {

    var $q: ng.IQService = $injector.get('$q');
    var localStorageService: ng.local.storage.ILocalStorageService = $injector.get('localStorageService');

    return {
        request: (config: ng.IRequestConfig): ng.IRequestConfig => {
            // check if headers are present
            config.headers = config.headers || {};

            // the error was here! I was trying to add properties to config that I think angular was not expecting
            // removing this line solved the issue
            // if (!config.bypassToken) {

            // check if user is logged in
            var loggedUserInfo = localStorageService.get<models.LoggedUserInfoModel>('Auth');
            if (loggedUserInfo) {
                config.headers['Authorization'] = 'Bearer ' + loggedUserInfo.access_token;
            }
            return config;
        },
        responseError: (rejection)  => {
            // check if user is logged in
            var loggedUserInfo = localStorageService.get<models.LoggedUserInfoModel>('Auth');
            if ((rejection.status === 401) && (loggedUserInfo)) {

                // if so, then the user must login againd
                localStorageService.remove('Auth');
                $location.path('/home');
                console.error(rejection);
            }
            return $q.reject(rejection);
        }
    };
}]);

I appreciate your help. I am only posting this here in case someone faces a similar issue. Don't mess with the config object!

like image 95
rodrigogq Avatar answered Nov 15 '22 06:11

rodrigogq