Redirecting to the Identity Server login page from the AngularJs website http web api request

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(); // create new identity and set name and role claim type var nid = new ClaimsIdentity( n.AuthenticationTicket.Identity.AuthenticationType, Thinktecture.IdentityServer.Core.Constants.ClaimTypes.GivenName, Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Role); userInfo.Claims.ToList().ForEach(c => nid.AddClaim(new Claim(c.Item1, c.Item2))); // keep the id_token for logout nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken)); // add access token for sample API nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken)); // keep track of access token expiration nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString())); // add some other app specific claim nid.AddClaim(new Claim("app_specific", "some data")); n.AuthenticationTicket = new AuthenticationTicket( nid, n.AuthenticationTicket.Properties); n.Request.Headers.SetValues("Authorization ", new string[] { "Bearer ", n.ProtocolMessage.AccessToken }); } } }; app.UseOpenIdConnectAuthentication(openIdConfig); app.UseResourceAuthorization(new AuthorizationManager()); app.Map("/api", inner => { var bearerTokenOptions = new IdentityServerBearerTokenAuthenticationOptions { Authority = "https://localhost:44301/identity", RequiredScopes = new[] { "baseballStatsApi" } }; inner.UseIdentityServerBearerTokenAuthentication(bearerTokenOptions); var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); inner.UseWebApi(config); }); } } 

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:

Http headers

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!

+6
source share
1 answer

Perhaps you have configured the server correctly, as you get the html login page as an answer to the angular $ http call -> this should work like this:

angularjs $ http

Note that if the response is a redirect, XMLHttpRequest will transparently follow it, which means that the result (success or error) will be determined by the final response status code.

You get a 200 OK response, since this is the final answer when the redirect is immediately executed, and the result is resolved as the result of the $ http service, and also the response headers have the final answer


One way to achieve the desired result is by redirecting the browser to the login page:

Instead of redirecting the request server side (from the web project to the identity server), the web api controller api/pitchingstats/GetFilteredPlayer can return an error response (401) with a json payload containing the {redirectUrl: 'login page'} field or the header, which can be read as response.headers('x-redirect-url') then go to the specified address using window.location.href = url

This logic can often be seen configured in $ httpInterceptors, which processes unauthorized access responses and redirects them to the login page - redirection is controlled on the client side

+1
source

Source: https://habr.com/ru/post/989869/


All Articles