title: ASP.NET Core policy-based authentication not working
body: <p>I have to manage our user's roles in our local database, not in Azure AD. But, I also need policy-based authorization for controllers because we have both admin and customer areas.</p>
  <p>To handle this, I added an authorization filter that loads the user's role from either <code>Session</code> or the database, adds an <code>Identity</code> to the <code>Principal</code>, and then moves along. This <code>Identity</code> adds an appropriate <code>Role</code> <code>Claim</code>.</p>
  <p>Before leaving the authorization filter, the <code>IsInRole</code> returns <code>true</code> as expected, and there are two <code>Identities</code>.</p>
  <p>My authorization filter looks like this:</p>
  <pre><code>public class MyAuthFilter : IAsyncAuthorizationFilter
  {
      private readonly IUserService userService;
  
      public MyAuthFilter(IUserService userService)
      {
          this.userService = userService;
      }
  
      public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
      {
          var user = context.HttpContext.User;
  
          if (user.Identity.IsAuthenticated)
          {
              AuthUserViewModel authUserViewModel;
  
              var sessionViewModelJson = context.HttpContext.Session.GetString(user.AzureObjectId());
              if (string.IsNullOrEmpty(sessionViewModelJson))
              {
                  authUserViewModel = await ConstructSessionViewModel(context);
              }
              else
              {
                  authUserViewModel = JsonConvert.DeserializeObject&lt;AuthUserViewModel&gt;(sessionViewModelJson);
              }
  
              user.AddIdentity(authUserViewModel?.Role);
          }
      }
  
      private async Task&lt;AuthUserViewModel&gt; ConstructSessionViewModel(AuthorizationFilterContext context)
      {
          var user = context.HttpContext.User;
          var parsedObjectId = Guid.Parse(user.AzureObjectId());
  
          var findUserResult = await userService.FindByAzureObjectId(new FindByAzureObjectIdRequest
          {
              AzureObjectId = parsedObjectId
          });
  
          if (findUserResult.Success)
          {
              var userModel = findUserResult.User;
  
              var viewModel = new AuthUserViewModel
              {
                  AzureObjectId = parsedObjectId,
                  UserId = userModel.Id,
                  SchoolId = userModel.SchoolId.GetValueOrDefault(),
                  Name = userModel.Name,
                  Email = userModel.Email,
                  PhoneNumber = userModel.PhoneNumber,
                  Role = userModel.Role
              };
  
              context.HttpContext.Session.SetString(user.AzureObjectId(), JsonConvert.SerializeObject(viewModel));
  
              return viewModel;
          }
  
          return null;
      }
  }
  </code></pre>
  <p>That <code>AddIdentity</code> extension method looks like this:</p>
  <pre><code>public static void AddIdentity(this ClaimsPrincipal principal, string role)
  {
      if (string.IsNullOrWhiteSpace(role) || principal.IsInRole(role))
      {
          return;
      }
  
      switch (role)
      {
          case Roles.School:
              principal.AddIdentity(new SchoolIdentity());
              break;
          case Roles.Admin:
              principal.AddIdentity(new AdminIdentity());
              break;
      }
  }
  </code></pre>
  <p>and in this case, the <code>SchoolIdentity</code> is what gets added, and it looks like this:</p>
  <pre><code>public class SchoolIdentity : ClaimsIdentity
  {
      public SchoolIdentity()
      {
          AddClaim(new SchoolPortalClaim());
      }
  }
  </code></pre>
  <p>and finally, the <code>SchoolPortalClaim</code> looks like this:</p>
  <pre><code>public class SchoolPortalClaim : Claim
  {
      public SchoolPortalClaim() : base(ClaimTypes.Role, &quot;School&quot;)
      {
      }
  
      public SchoolPortalClaim(BinaryReader reader) : base(reader)
      {
      }
  
      public SchoolPortalClaim(BinaryReader reader, ClaimsIdentity subject) : base(reader, subject)
      {
      }
  
      protected SchoolPortalClaim(Claim other) : base(other)
      {
      }
  
      protected SchoolPortalClaim(Claim other, ClaimsIdentity subject) : base(other, subject)
      {
      }
  
      public SchoolPortalClaim(string type, string value) : base(type, value)
      {
      }
  
      public SchoolPortalClaim(string type, string value, string valueType) : base(type, value, valueType)
      {
      }
  
      public SchoolPortalClaim(string type, string value, string valueType, string issuer) : base(type, value, valueType, issuer)
      {
      }
  
      public SchoolPortalClaim(string type, string value, string valueType, string issuer, string originalIssuer) : base(type, value, valueType, issuer, originalIssuer)
      {
      }
  
      public SchoolPortalClaim(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject) : base(type, value, valueType, issuer, originalIssuer, subject)
      {
      }
  }
  </code></pre>
  <p>The issue comes when the policy executes:</p>
  <pre><code>services.AddAuthorization(options =&gt;
  {
      options.AddPolicy(Policies.School,
          policy =&gt; policy.RequireAssertion(
              context =&gt; context.User.IsInRole(Roles.School)));
  });
  </code></pre>
  <p>The <code>context.User</code> does not have the <code>Identity</code> that was added by the authorization filter.</p>
  <p>How do I get this to move downstream?</p>
  <p>The <code>Controller</code> in question looks like this:</p>
  <pre><code>[Area(Areas.School)]
  [Authorize(Policy = Policies.School)]
  public class HomeController : BaseController
  {
      public HomeController(IUserService userService) :
          base(userService)
      {
      }
  
      public IActionResult Index()
      {
          return RedirectToAction(&quot;Index&quot;, &quot;Presentation&quot;, new {Area = &quot;School&quot;});
      }
  }
  </code></pre>