title: In asp.net core, why is await context.ChallengeAsync() not working as expected?
body: <p>I have two questions, both of which refer to the code below:</p>
  
  <ol>
  <li><p>Why is authenticateResult.Succeeded false after I call authenticateResult = await context.AuthenticateAsync();?</p></li>
  <li><p>Why do I need to call "return" from my custom middleware InvokeAsync method for this to work properly?</p></li>
  </ol>
  
  <p>I have an asp.net core application using OpenIdConnect. The application has two controller actions; both of them have the [Authorize] attribute, so when the application starts the user is automatically put through the OpenIdConnect process.  This works fine.</p>
  
  <p>Here is how I configure my OpenIdConnect middleware, I happen to be using PingOne:</p>
  
  <pre><code>            services.AddAuthentication(authenticationOptions =&gt;
              {
                  authenticationOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                  authenticationOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
              })
              .AddCookie()
              .AddOpenIdConnect(openIdConnectOptions =&gt;
              {
                  openIdConnectOptions.Authority = Configuration["PingOne:Authority"];
                  openIdConnectOptions.CallbackPath = Configuration["PingOne:CallbackPath"];
                  openIdConnectOptions.ClientId = Configuration["PingOne:ClientId"];
                  openIdConnectOptions.ClientSecret = Configuration["PingOne:ClientSecret"];
  
                  openIdConnectOptions.ResponseType = Configuration["PingOne:ResponseType"];
                  openIdConnectOptions.Scope.Clear();
                  foreach (var scope in scopes.GetChildren())
                  {
                      openIdConnectOptions.Scope.Add(scope.Value);
                  }
              });
  </code></pre>
  
  <p>Immediately after a user authenticates I redirect the user to another website (which uses the same OpenIdConnect authentication).  On "OtherWebsite" the user selects various options and then gets redirected back to the "OriginalWebsite" to a special path called "ReturningFromOtherWebsite". On return to OriginalWebSite I read the querystring, load some claims into the user's principal identity based on the querystring, and set a Session variable so that I know I've visited OtherWebSite once. </p>
  
  <p>I do not actually have a Controller method called "ReturningFromOtherWebsite" in OriginalWebSite, so I need to look for that path in my middleware and intercept handling of it.</p>
  
  <p>I decided to wrap this functionality in custom middleware I call "AfterAuthenticationMiddleware", which looks like this.  My questions are marked by the comments that start with "//QUESTION:..."</p>
  
  <pre><code>public class AfterAuthenticationMiddleware
  {
      private readonly RequestDelegate _next;
      private readonly IConfiguration Configuration;
      private IMembershipRepository MembershipRepository;
  
      public AfterAuthenticationMiddleware(RequestDelegate next, 
          IConfiguration configuration)
      {
          _next = next;
          Configuration = configuration;
      }
  
      private void SignInWithSelectedIdentity(Guid userId, 
          ClaimsIdentity claimsIdentity,
          AuthenticateResult authenticateResult,
          HttpContext context)
      {
          string applicationName = Configuration["ApplicationName"];
  
          List&lt;string&gt; roles = MembershipRepository.GetRoleNamesForUser(userId, applicationName);
  
          foreach (var role in roles)
          {
              claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
          }
  
          //add the claim to the authentication cookie
          context.SignInAsync(authenticateResult.Principal, authenticateResult.Properties);
      }
  
  
      public async Task InvokeAsync(HttpContext context, 
          IMembershipRepository membershipRepository)
      {
          MembershipRepository = membershipRepository;
  
          bool isIdentitySelected = context.Session.GetBoolean("IsIdentitySelected").GetValueOrDefault();
  
          if (isIdentitySelected)
          {
              //I know from existence of Session variable that there is no work to do here.
              await _next(context);
              return;
          }
  
          var authenticateResult = await context.AuthenticateAsync();
          ClaimsIdentity claimsIdentity = null;
  
          //the Controller action ReturningFromOtherWebSite does not actually exist.
          if (context.Request.Path.ToString().Contains("ReturningFromOtherWebSite"))
          {
              if (!authenticateResult.Succeeded)
              {
                  //this next line triggers the OpenIdConnect process
                  await context.ChallengeAsync();
  
                  //QUESTION: If I re-fetch the authenticateResult here, why is IsSucceeded false, for example:
                  //var authenticateResult = await context.AuthenticateAsync();
  
                  //QUESTION: why is the next line needed for this to work
                  return;
  
  
              }
  
              claimsIdentity = (ClaimsIdentity)authenticateResult.Principal.Identity;
  
              //set the Session variable so that on future requests we can bail out of this method quickly.
              context.Session.SetBoolean(Constants.IsIdentitySelected, true);
              var request = context.Request;
  
              //load some claims based on what the user selected in "OtherWebSite"
              string selectedIdentity = request.Query["selectedIdentity"];
  
              if (!Guid.TryParse(selectedIdentity, out Guid userId))
              {
                  throw new ApplicationException(
                      $"Unable to parse Guid from 'selectedIdentity':{selectedIdentity} ");
              }
  
              SignInWithSelectedIdentity(userId, claimsIdentity, authenticateResult, context);
  
              //redirect user to the page that the user originally requested
              string returnUrl = request.Query["returnUrl"];
              if (string.IsNullOrEmpty(returnUrl))
                  throw new ApplicationException(
                      $"Request is ReturnFromIdentityManagement but missing required parameter 'returnUrl' in querystring:{context.Request.QueryString} ");
  
              string path = $"{request.Scheme}://{request.Host}{returnUrl}";
  
              Log.Logger.Verbose($"AfterAuthentication InvokeAsync Redirect to {path}");
              context.Response.Redirect(path);
              //I understand why I call "return" here; I just want to send the user on to the page he/she originally requested without any more middleware being invoked
              return;
          }
  
          if (!authenticateResult.Succeeded)
          {
              //if the user has not gone through OIDC there is nothing to do here
              await _next(context);
              return;
          }
  
          //if get here it means user is authenticated but has not yet selected an identity on OtherWebSite
          claimsIdentity = (ClaimsIdentity)authenticateResult.Principal.Identity;
  
          Log.Logger.Verbose($"AfterAuthentication InvokeAsync check if redirect needed.");
          var emailClaim = claimsIdentity.Claims.FirstOrDefault(o =&gt; o.Type == ClaimTypes.Email);
          if(emailClaim == null)
              throw new ApplicationException($"User {authenticateResult.Principal.Identity.Name} lacks an Email claim");
  
          string emailAddress = emailClaim.Value;
          if(string.IsNullOrWhiteSpace(emailAddress))
              throw new ApplicationException("Email claim value is null or whitespace.");
  
          string applicationName = Configuration["ApplicationName"];
          if(string.IsNullOrEmpty(applicationName))
              throw new ApplicationException("ApplicationName missing from appsettings.json.");
  
          //if there is just one userid associated with the email address, load the claims.  if there is
          //more than one the user must redirect to OtherWebSite and select it
          List&lt;Guid?&gt; userIds =
              MembershipRepository.IsOtherWebsiteRedirectNeeded(emailAddress, applicationName);
  
          if (userIds == null
              || userIds[0] == null
              || userIds.Count &gt; 1)
          {
              //include the path the user was originally seeking, we will redirect to this path on return
              //cannot store in session (we lose session on the redirect to other web site)
              string queryString =
                  $"emailAddress={emailAddress}&amp;applicationName={applicationName}&amp;returnUrl={context.Request.Path}";
  
              context.Response.Redirect($"https://localhost:44301/Home/AuthenticatedUser?{queryString}");
          }
          else
          {
              SignInWithSelectedIdentity(userIds[0].Value, claimsIdentity, authenticateResult, context);
          }
  
          await _next(context);
      }
  }
  </code></pre>
  
  <p>And then I add the middlewares in the Configure method in the usual way:</p>
  
  <pre><code>app.UseAuthentication();
  app.UseAfterAuthentication();
  app.UseAuthorization();
  </code></pre>
  
  <p>I added the "return" call out of desperation and was shocked to discover that it fixed the problem, but I won't feel comfortable until I know <strong>why</strong> it fixed the problem. </p>