I'm creating an AngularJS (Typescript) SPA with a WebAPI2 backend, requiring authentication and authorization from the API. The API is hosted on a different server, so I'm using CORS, mainly following the guidance found at http://www.codeproject.com/Articles/742532/Using-Web-API-Individual-User-Account-plus-CORS-En as I'm a newcomer in this field.
All works fine, I can register and login, and then make requests to restricted-access controller actions (here the dummy "values" controller from the default VS WebAPI 2 template) by passing the received access token, in a client-side service with this relevant code:
private buildHeaders() {
if (this.settings.token) {
return { "Authorization": "Bearer " + this.settings.token };
}
return undefined;
}
public getValues(): ng.IPromise<string[]> {
var deferred = this.$q.defer();
this.$http({
url: this.config.rootUrl + "api/values",
method: "GET",
headers: this.buildHeaders(),
}).success((data: string[]) => {
deferred.resolve(data);
}).error((data: any, status: any) => {
deferred.reject(status.toString() + " " +
data.Message + ": " +
data.ExceptionMessage);
});
return deferred.promise;
}
Now, I'd like to retrieve the user's roles once logged in so that the AngularJS app can behave accordingly. Thus I added this method in my account API (which at the class level has attributes [Authorize]
, [RoutePrefix("api/Account")]
, [EnableCors(origins: "*", headers: "*", methods: "*")]
(*
are for testing purposes):
[Route("UserRoles")]
public string[] GetUserRoles()
{
return UserManager.GetRoles(User.Identity.GetUserId()).ToArray();
}
I then added this code to my login controller:
private loadUserRoles() {
this.accountService.getUserRoles()
.then((data: string[]) => {
// store roles in an app-settings service
this.settings.roles = data;
}, (reason) => {
this.settings.roles = [];
});
}
public login() {
if ((!this.$scope.name) || (!this.$scope.password)) return;
this.accountService.loginUser(this.$scope.name,
this.$scope.password)
.then((data: ILoginResponseModel) => {
this.settings.token = data.access_token;
// LOAD ROLES HERE
this.loadUserRoles();
}, (reason) => {
this.settings.token = null;
this.settings.roles = [];
});
}
where the account controller's method is:
public getUserRoles() : ng.IPromise<string[]> {
var deferred = this.$q.defer();
this.$http({
url: this.config.rootUrl + "api/account/userroles",
method: "GET",
headers: this.buildHeaders()
}).success((data: string[]) => {
deferred.resolve(data);
}).error((data: any, status: any) => {
deferred.reject(status.toString() + ": " +
data.error + ": " +
data.error_description);
});
return deferred.promise;
}
Anyway this triggers an OPTIONS preflight request, which in turn causes a 500 error. If I inspect the response, I can see that the GetOwinContext method gets a null request. Here is the beginning of the error stack trace:
{"message":"An error has occurred.","exceptionMessage":"Value cannot be null.\r\nParameter name: request","exceptionType":"System.ArgumentNullException","stackTrace":" at System.Net.Http.OwinHttpRequestMessageExtensions.GetOwinContext(HttpRequestMessage request)\r\n at Accounts.Web.Controllers.AccountController.get_UserManager() ...}
Yet, the code I'm using for GETting the roles is no different from that I use for GETting the dummy "values" from the WebAPI test controller. I can't exactly see the reason why a preflight should be required here, but in any case I'm getting this nasty exception in OWIN code.
My request header is (the API being at port 49592):
OPTIONS /api/account/userroles HTTP/1.1
Host: localhost:49592
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:64036
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Access-Control-Request-Headers: accept, authorization
Accept: */*
Referer: http://localhost:64036/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,it;q=-5.4
Could anyone explain?