Search code examples
restasp.net-coreauthorizationkeycloakenforcement

Keycloak UMA Authorization REST API requires to send thousands requests


I have configured Keycloak and public client which is just react frontend app and web-api backend. Authentication works fine. User open front page, redirect to Keycloak login\password form, get his access_token and do some requests to web-api with that access_token in the header. Looks nice, works as expected.

Now I want to do more complicated case. My application needs to authorize user to specific objects like below. For example, I have object with URL /api/v1/school/123 and want to User1 be able to get it, but not User2. And every owner of such 'school' object decide for whom he wants to grant permissions. Looks like ACL, right? I've found such process like UMA authorization and Keycloak can do this (from the first glance at least).

Now it's become interesting.

Additionally I created keycloak confidential client, enabled Authorization for it, UMA and token exchange features. According to the documentation https://www.keycloak.org/docs/latest/authorization_services/#_service_overview I need to send request to /token endpoint and get RPT back (or just decision with 'response_mode' = 'decision') in the end. To do that first of all I need to get PAT token:

// Getting PAT
TokenResponse tokenResponse = await _tokenService.RequestToken(new TokenRequest()
{
    GrantType = GrantTypes.ClientCredentials,
    ClientId = _options.ClientId,
    ClientSecret = _options.ClientSecret
}, null);
var PATAuthzHeader = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken);

After getting PAT I able to 'find' real resource Id by request URL:

// We have to find resource by it's uri, because user or client doesn't know anything about resources. They have request path only.
var resources = await _resourceService.FindResourceAsync(null, null, requestPath,
  null, null, null, null, null, PATAuthzHeader);
if (resources.Count == 0)
{
    return false;
}

With resource Id we can finally do request:

var currentAuthorizationHeader = AuthenticationHeaderValue.Parse(context.HttpContext.Request.Headers["Authorization"]);
// With resource id we can check permissions by sending RPT request to token endpoint
// We also send request's method (get, post and so on) to separate CRUD operations
var decisionResponse = await _tokenService.GetRPTDecision(new TokenRequest()
{
  GrantType = GrantTypes.UmaTicket,
  ResponseMode = ResponseMode.Decision,
  Audience = _options.ClientId,
  Permission = $"{resourceId}#{context.HttpContext.Request.Method.ToLower()}"
}, currentAuthorizationHeader);
// If the authorization request does not map to any permission, a 403 HTTP status code is returned instead.

Finally we do this! Great! Now everything begins...

As you might notice we have to find resource id by it's URL before we can check permissions. Tricky part is that we can create (and probably will do) resources with wildcard URIs, like /api/v1/schools/123/*, to grant owner comprehensive permissions to every child object of the school with number '123'. It means when owner send request to /api/v1/schools/123/classes/3/students he should be permitted to do this and get response. You will ask: "Why don't you create resource with that specific URI in advance?". I tried, but I can't do this at the moment of creating school with number '123'. After all Keycloak told us it support wildcards in URI. But request to https://www.keycloak.org/docs/latest/authorization_services/#querying-resources endpoint with such url '/api/v1/schools/123/classes/3/students' return empty array. It can not find my resource.

At that point I understand that Keycloak is not so good as I imagine. I was tried to find any documentation that would help me, but they don't have it. Everything I've got is answer on github: "Look our sources.". Great. Digging java sources (just remind I use asp net core) is not what I looking for. I had no other options, but to implement Policy Enforcer by myself.

Like Murphy's law says: Anything that can go wrong will go wrong.

Now it became more and more unpleased.

So what is my image of enforcer? It's just a magic box that load every resource, policy and permission from Keycloak Server and works like a proxy between application and Keycloak Server evaluating requests inside, and from time to time syncing itself with Keycloak Server to get or send changes. So, let's do this! But wait, how can I grab all resources from Keycloak's client (or resource server precisely)? Querying them from https://www.keycloak.org/docs/latest/authorization_services/#querying-resources returns just banch of guids like this:

[
  "f8cc15ad-b2e5-46f3-88c6-e0e7cd2ffe4d",
  "540aafb9-3c3a-4a99-a6d2-c089c607d5ec",
  "9bdf0702-4ee3-421e-9ac8-6ea1b699e073",
  "838a6194-3153-473e-9b0b-4f395f49d5cb"
]

But I need resource's URI! Give it to me!

And now I'm here. Asking you if there any other way to get it, but not sending separate request for every given guid? It even would be better if you know a shorter way to authorize user with keycloak REST API?


Solution

  • To evaluate wildcard permissions, use the matchingUri parameter.

    GET https://example.com/realms/example/authz/protection/resource_set?uri=/api/v1/schools/123/classes/3/students&matchingUri=true

    To retrieve not only the ID, but also the full json, use the deep parameter.

    GET https://example.com/realms/example/authz/protection/resource_set?uri=/api/v1/schools/123/classes/3/students&matchingUri=true&deep=true

    Found here: Fetch resource given partial url path or based on a regex pattern using keycloak rest admin apis