I am trying to redirect to the default Identity Server login page when I call the API controller method from Angular $ http service.
My web project and Identity Server are in different projects and have different Startup.cs files.
The web project Statup.cs is as follows
public class Startup { public void Configuration(IAppBuilder app) { AntiForgeryConfig.UniqueClaimTypeIdentifier = Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Subject; JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>(); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "Cookies", }); var openIdConfig = new OpenIdConnectAuthenticationOptions { Authority = "https://localhost:44301/identity", ClientId = "baseballStats", Scope = "openid profile roles baseballStatsApi", RedirectUri = "https://localhost:44300/", ResponseType = "id_token token", SignInAsAuthenticationType = "Cookies", UseTokenLifetime = false, Notifications = new OpenIdConnectAuthenticationNotifications { SecurityTokenValidated = async n => { var userInfoClient = new UserInfoClient( new Uri(n.Options.Authority + "/connect/userinfo"), n.ProtocolMessage.AccessToken); var userInfo = await userInfoClient.GetAsync();
You will notice that the API is protected by bearer authentication, while the rest of the application uses OpenIdConnect.
Identity Server Startup.cs Class
public class Startup { public void Configuration(IAppBuilder app) { var policy = new System.Web.Cors.CorsPolicy { AllowAnyOrigin = true, AllowAnyHeader = true, AllowAnyMethod = true, SupportsCredentials = true }; policy.ExposedHeaders.Add("Location"); app.UseCors(new CorsOptions { PolicyProvider = new CorsPolicyProvider { PolicyResolver = context => Task.FromResult(policy) } }); app.Map("/identity", idsrvApp => { idsrvApp.UseIdentityServer(new IdentityServerOptions { SiteName = "Embedded IdentityServer", SigningCertificate = LoadCertificate(), Factory = InMemoryFactory.Create( users: Users.Get(), clients: Clients.Get(), scopes: Scopes.Get()) }); }); } X509Certificate2 LoadCertificate() { return new X509Certificate2( string.Format(@"{0}\bin\Configuration\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test"); } }
Note that I added a CorsPolicy entry so that the web application might be redirected to the login page. In addition, the Cors policy provides a location request header, as it contains a URL that I would like to redirect to.
The Web Api controller method is protected using the Authorize attribute, thus
[HttpPost] [EnableCors(origins: "*", headers: "*", methods: "*")] [Authorize] public PlayerData GetFilteredPlayers(PlayerInformationParameters parameters) { var playerInformation = composer.Compose<PlayerInformation>().UsingParameters(parameters); var players = playerInformation.Players .Select(p => new { p.NameLast, p.NameFirst, p.Nickname, p.BirthCity, p.BirthState, p.BirthCountry, p.BirthDay, p.BirthMonth, p.BirthYear, p.Weight, p.Height, p.College, p.Bats, p.Throws, p.Debut, p.FinalGame }); var playerData = new PlayerData { Players = players, Count = playerInformation.Count, Headers = GetHeaders(players) }; return playerData; }
Angular factory makes $ http call as below
baseballApp.factory('playerService', function ($http, $q) { return { getPlayerList: function (queryParameters) { var deferred = $q.defer(); $http.post('api/pitchingstats/GetFilteredPlayers', { skip: queryParameters.skip, take: queryParameters.take, orderby: queryParameters.orderby, sortdirection: queryParameters.sortdirection, filter: queryParameters.filter }).success(function (data, status) { deferred.resolve(data); }).error(function (data, status) { deferred.reject(status); }); return deferred.promise; } }});
When this call occurs, the response state is 200, and html for the login page is returned in the data.
In addition, on the "Chrome Network" tab, you can see that the response has a "Place" heading with the URL of the "Login" page. However, if I configured the http interceptor, I see that the Accept header was passed in javascript.
Here are the http headers displayed on the Chrome network tab:

For some reason, the response does not have an Access-Control-Allow-Origin header.
Therefore, I have the following questions:
Is there a way to access the response location header in Angular client code to redirect to it?
How can I get a server to send me 401 instead of 200 to find out that there was an authentication error?
Is there a better way to do this, and if so, how?
Thank you for your help!
EDIT:
I added a custom attribute, AuthorizeAttribute, to determine which HTTP status code is returned from the filter.
Custom Filter Code
public class BearerTokenAutorizeAttribute : AuthorizeAttribute { private const string AjaxHeaderKey = "X-Requested-With"; private const string AjaxHeaderValue = "XMLHttpRequest"; protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext) { var headers = actionContext.Request.Headers; if(IsAjaxRequest(headers)) { if (actionContext.RequestContext.Principal.Identity.IsAuthenticated) actionContext.Response.StatusCode = System.Net.HttpStatusCode.Forbidden; else actionContext.Response.StatusCode = System.Net.HttpStatusCode.Unauthorized; } base.HandleUnauthorizedRequest(actionContext); var finalStatus = actionContext.Response.StatusCode; } private bool IsAjaxRequest(HttpRequestHeaders requestHeaders) { return requestHeaders.Contains(AjaxHeaderKey) && requestHeaders.GetValues(AjaxHeaderKey).FirstOrDefault() == AjaxHeaderValue; }
I noticed two things from this: first, the X-Requested-With header is not included in the request generated by the $ http client-side service. Moreover, the final http status returned by the base method is 401 - Unauthorized. This means that the status code is changing somewhere in the chain.
Please do not feel that you have to answer all the questions. Any help would be greatly appreciated!