✨ Improve authorization performance
Hi all!
I've been looking into the IdentityService and specifically the AuthorizeAsync and IsInRolesAsync method that I use extensively to authorize the current user's commands and queries.
Each authorization is quite expensive since each request has to make at least one roundtrip to the database to fetch the user by id to create a ClaimsPrincipal. This of course is fine if we need to authorize any arbitrary user, but for the current user this seem unnecessary as we already have a claims principal - after all, that's where we extract the user id from in the first place in the CurrentUser service.
Would it not be better to use the claims principal we already have as much as possible? Especially for the AuthorizationBehaviour that might have a high hit rate.
This could be solved in a few ways;
- Add
AuthorizeAsyncmethod to theIUserservice. I think this would be the nicest API, but for some reason does not work for me, as the application hangs (not crashes) when injectingIAuthorizationServiceintoCurrentUser. - Expose a
ClaimsPrincipalproperty on theIUserservice, and add a newAuthorizeAsyncoverload to theIdentityServicethat accepts aClaimsPrincipalinstead of a user id. - ...?
Do you see any issues with this approach, or have better proposals?
Thanks
To add one more point: If I have an Authorize attribute with multiple Roles, like [Authorize(Roles = "Foo, Bar, Baz")] generates two database queries for each role request - one for getting the user, and one for getting the role information.
The quick fix would be to change _userManager.Users.SingleOrDefault(u => u.Id == userId) and _userManager.Users.FirstAsync(u => u.Id == userId) to _userManager.FindByIdAsync(userId) because the SQL UserStore calls FindAsync, and that call is cached because it's getting the database record by it's PK.
The PR I sent reduces the number of select * from [AspNetUsers] where Id = xxxx db queries to one.
But!
The user and role information is already present in the ClaimsPrincipal:
And can be queried like
_httpContextAccessor.HttpContext?.User.IsInRole("Administrator")
So some refactoring might be justified? Or is there some specific issue you tried to avoid organizing code this way? I do remember some weird issues the ClaimsPrincipal not noticing Role changes, but that could have been because I was hacking directly into the database...