title: How to Release a change that renames an User Role name
body: <p>We're working on changes to an ASP.NET MVC app.
  We're using Owin and OAuth2 to manage User permissions, but are managing the User DB object ourselves.</p>
  
  <p>We have these on App Startup:</p>
  
  <pre><code>app.UseKentorOwinCookieSaver();
  app.UseCookieAuthentication(GetCookieAuthenticationOptions(AuthenticationType))
     .UseOpenIdConnectAuthentication(GetOpenIdConnectOptions(AuthenticationType));
  </code></pre>
  
  <p>And we manually assign Claims to users when they log in <code>Role</code> is an <code>enum</code>:</p>
  
  <pre><code>claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, user.Role.ToString()));
  </code></pre>
  
  <p><em>If more detail is needed, the auth code is included at the end.</em></p>
  
  <p>All of this is has been working fine, but we need to rename a role.
  The code rename is trivial, and it all works just fine when I log in after the role is renamed. But if I'm already logged in, when the code changes, then my old role Claim string is still in my Auth Cookie, and is no longer recognised by the Auth code.</p>
  
  <p>Becuase I'm already logged in, it doesn't take me to the LogIn page - it just shows me the "Forbidden" error page (As though I'd entered a link to a page I shouldn't have visited)</p>
  
  <p>And because our Auth works by checking whether you have "Role '<em>x</em>' or any Role greater than '<em>x</em>'", thus we get Forbidden on <strong>every</strong> page (because now the user doesn't have <strong>any</strong> Role and thus fails <strong>every</strong> Auth test, because their Role isn't recognised as passing <strong>any</strong> test.</p>
  
  <p>As a result the user has no way to log out.</p>
  
  <p>As a developer, I can wipe my browser cookies and log in from scratch (at which point it works just fine) but a normal user (probably?) won't be able to do that.</p>
  
  <p>My first thought was do somehting like this: <a href="http://www.britishdeveloper.co.uk/2010/09/force-client-refresh-browser-cache.html" rel="nofollow noreferrer">http://www.britishdeveloper.co.uk/2010/09/force-client-refresh-browser-cache.html</a>, to all users to log out and get them to log in again, once after the release.
  Unfortunately, since EVERY page will fail, I've got nowhere to <em>put</em> that code that will run for the relevant users :(</p>
  
  <p>I could hack around with the Authentication Code so that it knows about the old Roles and grants that Claim permission, but that seem <em>hideous</em>.</p>
  
  <p>Another option would be to modify the Authorisation code so that it logged users out if they don't have any recognised Roles, but that doesn't really feel right either, for some reason I can't put my finger on.</p>
  
  <p>Any suggestions or opinions about the <strong>right</strong> way to release such a change?</p>
  
  <p>=-=-=-=-=-=-=-=-=-=</p>
  
  <p>Auth code:</p>
  
  <pre><code>private const string AuthenticationType = "FrontEnd" + CookieAuthenticationDefaults.AuthenticationType;
  private const string IdTokenClaimName = "id_token";
  
  public void Configuration(IAppBuilder app)
  {
      app.UseKentorOwinCookieSaver();
      app.UseCookieAuthentication(GetCookieAuthenticationOptions(AuthenticationType))
         .UseOpenIdConnectAuthentication(GetOpenIdConnectOptions(AuthenticationType));
  }
  
  private static CookieAuthenticationOptions GetCookieAuthenticationOptions(string authenticationType)
  {
      return new CookieAuthenticationOptions
         {
             AuthenticationType = authenticationType,
         };
  }
  
  private OpenIdConnectAuthenticationOptions GetOpenIdConnectOptions(string authenticationType)
  {
      return new OpenIdConnectAuthenticationOptions
             {
                 Authority = AuthenticationConstants.AuthenticationAuthority,
                 ClientId = AuthenticationConstants.ClientId,
                 RedirectUri = AuthenticationConstants.ClientRedirectUrl,
                 ResponseType = "id_token",
                 Scope = "openid profile email",
                 SignInAsAuthenticationType = authenticationType,
                 UseTokenLifetime = false,
                 Notifications = new OpenIdConnectAuthenticationNotifications
                                 {
                                     SecurityTokenValidated = n =&gt; Task.Run(() =&gt; AuthorizeIfUserExists(n)),
                                     RedirectToIdentityProvider = n =&gt; Task.Run(() =&gt; SendIdTokenToLogout(n))
                                 }
             };
   }
  
  private static void SendIdTokenToLogout(RedirectToIdentityProviderNotification&lt;OpenIdConnectMessage, OpenIdConnectAuthenticationOptions&gt; n)
  {
      if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
      {
          var idTokenHint = n.OwinContext.Authentication.User.FindFirst(IdTokenClaimName).Value;
          n.ProtocolMessage.IdTokenHint = idTokenHint;
      }
  }
  
  private void AuthorizeIfUserExists(SecurityTokenValidatedNotification&lt;OpenIdConnectMessage, OpenIdConnectAuthenticationOptions&gt; authContext)
  {
      var identity = authContext.AuthenticationTicket.Identity;
      var userIdClaim = GetUserIdClaim(identity);
      var emailClaim = GetEmailClaim(identity);
  
      var claimsIdentity = new ClaimsIdentity(
          identity.AuthenticationType,
          ClaimTypes.Name,
          ClaimTypes.Role);
  
      claimsIdentity.AddClaim(new Claim(IdTokenClaimName, authContext.ProtocolMessage.IdToken));
      claimsIdentity.AddClaim(userIdClaim);
      claimsIdentity.AddClaim(emailClaim);
  
      using (var context = new DbDataContext())
      {
          var user = GetAndInitializeUserIfNecessary(context, userIdClaim.Value, emailClaim.Value);
  
          // We add role and name claims to all successful logins that are also registered in our database.
          if (user != null &amp;&amp; !user.IsSoftDeleted)
          {
              claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, user.Role.ToString()));
              claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, String.Format("{0} {1}", user.FirstName, user.Surname)));
          }
      }
  
      authContext.AuthenticationTicket = new AuthenticationTicket(
          claimsIdentity,
          authContext.AuthenticationTicket.Properties);
  }
  </code></pre>