title: Invalid Access Token/Missing Claims when logged into IdentityServer4
body: <p>I have a standard .NET Core 2.1 (MVC and API) and Identity Server 4 project setup.
  I am using reference tokens instead of jwt tokens.</p>
  
  <p>The scenario is as follows:    </p>
  
  <ul>
  <li>Browse to my application</li>
  <li>Redirected to Identity Server</li>
  <li>Enter valid valid credentials</li>
  <li>Redirected back to application with all claims (roles) and correct access to the application and API</li>
  </ul>
  
  <p>Wait an undetermined amount of time (I think it's an hour, I don't have the    exact timing)</p>
  
  <ul>
  <li>Browse to my application</li>
  <li>Redirected to Identity Server</li>
  <li><strong>I'm still logged into the IDP</strong> so I'm redirected immediately back to my
  application</li>
  <li>At this point the logged in .NET user is missing claims (roles) and no longer has access to the API</li>
  </ul>
  
  <p>The same result happens if I delete all application cookies  </p>
  
  <p>It seems obvious to me that the access token has expired.  How do I handle this scenario?  I'm still logged into the IDP and the middleware automatically logged me into my application, however, with an expired (?) access token and missing claims.  </p>
  
  <p>Does this have anything to do with the use of reference tokens?  </p>
  
  <p>I'm digging through a huge mess of threads and articles, any guidance and/or solution to this scenario?</p>
  
  <p><strong>EDIT</strong>:  It appears my access token is valid.  I have narrowed my issue down to the missing user profile data.  Specifically, the role claim.  </p>
  
  <p>When I clear both my application and IDP cookies, everything works fine.  However, after "x" (1 hour?) time period, when I attempt to refresh or access the application I am redirected to the IDP then right back to the application.  </p>
  
  <p>At that point I have a valid and authenticated user, however, I am missing all my role claims.  </p>
  
  <p>How can I configure the AddOpenIdConnect Middleware to fetch the missing claims in this scenario?  </p>
  
  <p>I suppose in the OnUserInformationReceived event I can check for the missing "role" claim, if missing then call the UserInfoEndpoint...that seems like a very odd workflow.  Especially since on a "fresh" login the "role" claim comes back fine.  (Note: I do see the role claim missing from the context in the error scenario).</p>
  
  <p>Here is my client application configuration:  </p>
  
  <pre><code>services.AddAuthentication(authOpts =&gt;
          {
              authOpts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
              authOpts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
          })
          .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opts =&gt; { })
          .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, openIdOpts =&gt;
          {
              openIdOpts.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
              openIdOpts.Authority = settings.IDP.Authority;
              openIdOpts.ClientId = settings.IDP.ClientId;
              openIdOpts.ClientSecret = settings.IDP.ClientSecret;
              openIdOpts.ResponseType = settings.IDP.ResponseType;
  
              openIdOpts.GetClaimsFromUserInfoEndpoint = true;
              openIdOpts.RequireHttpsMetadata = false;
              openIdOpts.SaveTokens = true;
              openIdOpts.ResponseMode = "form_post";
  
              openIdOpts.Scope.Clear();
              settings.IDP.Scope.ForEach(s =&gt; openIdOpts.Scope.Add(s));
  
              // https://leastprivilege.com/2017/11/15/missing-claims-in-the-asp-net-core-2-openid-connect-handler/
              // https://github.com/aspnet/Security/issues/1449
              // https://github.com/IdentityServer/IdentityServer4/issues/1786
  
              // Add Claim Mappings
              openIdOpts.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username"); /* SID alias */
              openIdOpts.ClaimActions.MapJsonKey("role", "role", "role");
  
              openIdOpts.TokenValidationParameters = new TokenValidationParameters
              {
                  ValidAudience = settings.IDP.ClientId,
                  ValidIssuer = settings.IDP.Authority,
                  NameClaimType = "name",
                  RoleClaimType = "role"
              };
  
              openIdOpts.Events = new OpenIdConnectEvents
              {
                  OnUserInformationReceived = context =&gt;
                  {
                      Log.Info("Recieved user info from IDP.");
                      // check for missing roles?  they are here on a fresh login but missing
                      // after x amount of time (1 hour?)
                      return Task.CompletedTask;
                  },
                  OnRedirectToIdentityProvider = context =&gt;
                  {
                      Log.Info("Redirecting to identity provider.");
                      return Task.CompletedTask;
                  },
                  OnTokenValidated = context =&gt;
                      {
                          Log.Debug("OnTokenValidated");
                          // this addressed the scenario where the Identity Server validates a user however that user does not
                          // exist in the currently configured source system.
                          // Can happen if there is a configuration mismatch between the local SID system and the IDP Client
                          var validUser = false;
                          int uid = 0;
                          var identity = context.Principal?.Identity as ClaimsIdentity;
  
                          if (identity != null)
                          {
                              var sub = identity.Claims.FirstOrDefault(c =&gt; c.Type == "sub");
  
                              Log.Debug($"    Validating sub '{sub.Value}'");
  
                              if (sub != null &amp;&amp; !string.IsNullOrWhiteSpace(sub.Value))
                              {
  
                                  if (Int32.TryParse(sub.Value, out uid))
                                  {
                                      using (var configSvc = ApiServiceHelper.GetAdminService(settings))
                                      {
                                          try
                                          {
                                              var usr = configSvc.EaiUser.GetByID(uid);
  
                                              if (usr != null &amp;&amp; usr.ID.GetValueOrDefault(0) &gt; 0)
                                                  validUser = true;
                                          }
                                          catch { }
                                      }
                                  }
                              }
  
                              Log.Debug($"    Validated sub '{sub.Value}'");
                          }
  
                          if (!validUser)
                          {
                              // uhhh, does this work?  Logout?
                              // TODO: test!
                              Log.Warn($"Unable to validate user is SID for ({uid}).  Redirecting to '/Home/Logout'");
                              context.Response.Redirect("/Home/Logout?msg=User not validated in source system");
                              context.HandleResponse();
                          }
  
                          return Task.CompletedTask;
                      },
                  OnTicketReceived = context =&gt;
                  {
                      // TODO: Is this necessary?
                      // added the below code because I thought my application access_token was expired
                      // however it turns out I'm actually misisng the role claims when I come back to the
                      // application from the IDP after about an hour
                      if (context.Properties != null &amp;&amp;
                          context.Properties.Items != null)
                      {
                          DateTime expiresAt = System.DateTime.MinValue;
  
                          foreach (var p in context.Properties.Items)
                          {
                              if (p.Key == ".Token.expires_at")
                              {
                                  DateTime.TryParse(p.Value, null, DateTimeStyles.AdjustToUniversal, out expiresAt);
                                  break;
                              }
                          }
  
                          if (expiresAt != DateTime.MinValue &amp;&amp;
                              expiresAt != DateTime.MaxValue)
                          {
                              // I did this to synch the .NET cookie timeout with the IDP access token timeout?
                              // This somewhat concerns me becuase I thought that part should be done auto-magically already
                              // I mean, refresh token?
                              context.Properties.IsPersistent = true;
                              context.Properties.ExpiresUtc = expiresAt;
                          }
                      }
  
                      return Task.CompletedTask;
                  }
              };
          });
  </code></pre>