title: Handling Expired Refresh Tokens in ASP.NET Core
body: <p><strong>See below for code that solved this issue</strong></p>
  <p>I'm trying to find the best and most efficient way to deal with a refresh token that has expired within ASP.NET Core 2.1.</p>
  <p>Let me explain a bit more.</p>
  <p>I am using OAUTH2 and OIDC to request Authorization Code grant flows (or Hybrid flow with OIDC). This flow/grant type gives me access to an AccessToken, and a RefreshToken (Authorization Code as well, but that is not for this question).</p>
  <p>The access token and refresh token are stored by ASP.NET core, and can be retrieved using <code>HttpContext.GetTokenAsync(&quot;access_token&quot;);</code> and <code>HttpContext.GetTokenAsync(&quot;refresh_token&quot;);</code> respectively.</p>
  <p>I can refresh the <code>access_token</code> without any issues. The issue comes into play when the <code>refresh_token</code> is expired, revoked or invalid in some way.</p>
  <p>The correct flow would be to have the user log in and go back though the entire authentication flow again. Then the application gets a new set of tokens returned.</p>
  <p>My question is how can I achieve this in the best and most correct method. I decided to write a custom middleware that attempts to renew the <code>access_token</code> if it has expired. The middleware then sets the new token into the <code>AuthenticationProperties</code> for the HttpContext so it can be used by any calls later down the pipe.</p>
  <p>If refreshing the token fails for any reason, I need to call ChallengeAsync again. I am calling ChallengeAsync from the middleware.</p>
  <p>This is where I am running into some interesting behavior. Most of the time this works, however, sometimes I'll get 500 errors with no helpful information as to what is failing. It almost seems like the middleware is having issues trying to call ChallengeAsync from the middleware, and maybe another middleware is also trying to access the context.</p>
  <p>I'm not quite sure what is going on. I'm not quite sure if this is the right place to put this logic or not. Maybe I should not have this in middleware, maybe somewhere else. Maybe Polly for the HttpClient is the best place.</p>
  <p>I'm open for any ideas.</p>
  <p>Thanks for any help you can provide.</p>
  <p><strong>Code solution that worked for me</strong></p>
  <hr />
  <p>Thanks to <a href="https://stackoverflow.com/users/562839/micka%c3%abl-derriey">Mickaël Derriey</a> for the help and direction (be sure to see his answer for more information in the context of this solution). This is the solution that I've come up with, and it's working for me:</p>
  <pre class="lang-cs prettyprint-override"><code>options.Events = new CookieAuthenticationEvents
  {
      OnValidatePrincipal = context =&gt;
      {
          //check to see if user is authenticated first
          if (context.Principal.Identity.IsAuthenticated)
          {
              //get the user's tokens
              var tokens = context.Properties.GetTokens();
              var refreshToken = tokens.FirstOrDefault(t =&gt; t.Name == &quot;refresh_token&quot;);
              var accessToken = tokens.FirstOrDefault(t =&gt; t.Name == &quot;access_token&quot;);
              var exp = tokens.FirstOrDefault(t =&gt; t.Name == &quot;expires_at&quot;);
              var expires = DateTime.Parse(exp.Value);
              //check to see if the token has expired
              if (expires &lt; DateTime.Now)
              {
                  //token is expired, let's attempt to renew
                  var tokenEndpoint = &quot;https://token.endpoint.server&quot;;
                  var tokenClient = new TokenClient(tokenEndpoint, clientId, clientSecret);
                  var tokenResponse = tokenClient.RequestRefreshTokenAsync(refreshToken.Value).Result;
                  //check for error while renewing - any error will trigger a new login.
                  if (tokenResponse.IsError)
                  {
                      //reject Principal
                      context.RejectPrincipal();
                      return Task.CompletedTask;
                  }
                  //set new token values
                  refreshToken.Value = tokenResponse.RefreshToken;
                  accessToken.Value = tokenResponse.AccessToken;
                  //set new expiration date
                  var newExpires = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
                  exp.Value = newExpires.ToString(&quot;o&quot;, CultureInfo.InvariantCulture);
                  //set tokens in auth properties 
                  context.Properties.StoreTokens(tokens);
                  //trigger context to renew cookie with new token values
                  context.ShouldRenew = true;
                  return Task.CompletedTask;
              }
          }
          return Task.CompletedTask;
      }
  };
  </code></pre>