keycloak-community icon indicating copy to clipboard operation
keycloak-community copied to clipboard

Authorization Handler / Client Scopes enhancement for Dynamic Scopes / RAR

Open dgozalo opened this issue 3 years ago • 12 comments

After considering how Dynamic Scopes and RAR would interact with the system and the possible interactions between them, the team has decided to come up with a mechanism that will abstract the authorization mechanism used in the authorization request.

In this proposal, we’ll be merging the Dynamic Scopes discussion and the RAR Design Proposal into a single place.

So, many considerations, benefits and implementation details of both proposals can be considered true here, as well as some of the ideas proposed in the Dynamic Scopes vs RAR discussion.

dgozalo avatar Oct 14 '21 09:10 dgozalo

Hello @dgozalo, I'm @tnorimat working at FAPI-SIG (Financial-grade API Security : Special Interest Group) that tries to support RAR.

I've added some review comments. Could you check them?

Thank you for the thorough review @tnorimat. I've gone through all the comments and made a few modifications according to what was discussed with Marek.

Apologies for taking so long to come back to this, but we'll shift focus to this again soon.

dgozalo avatar Dec 14 '21 17:12 dgozalo

@dgozalo Thank you for your update. I've read your update and confirmed that points I've commented on have been incorporated.

I have 2 comments on that.

I think there is an authorization that can be used as one shot. For example,

{
   "type": "payment_initiation",
   "locations": [
       "https://example.com/payments"
   ],
   "instructedAmount": {
       "currency": "EUR",
       "amount": "125.00"
   }
}

When a client sends this authorization request to an authorization server, it gets a consent from a user and sends an access token to the client.

After that, when the client sends the same authorization request to the authorization server, it again gets a consent from a resource owner because this payment initiation is different from the former one.

Considering this point, IMO, such the request need not persist. Namely, there is an authorization that requires user consent but does not require persist.

What do you think about it?

I think we need to log an authorization, namely which client sends which authorization request and who authorized this request when, regardless of whether the authorization persists or not. I'm not sure this document should cover such logging but there is the case that such log can be used for audit.

tnorimat avatar Dec 17 '21 06:12 tnorimat

@dgozalo Thank you for your update. I've read your update and confirmed that points I've commented on have been incorporated.

I have 2 comments on that.

I think there is an authorization that can be used as one shot. For example,

{
   "type": "payment_initiation",
   "locations": [
       "https://example.com/payments"
   ],
   "instructedAmount": {
       "currency": "EUR",
       "amount": "125.00"
   }
}

When a client sends this authorization request to an authorization server, it gets a consent from a user and sends an access token to the client.

After that, when the client sends the same authorization request to the authorization server, it again gets a consent from a resource owner because this payment initiation is different from the former one.

Considering this point, IMO, such the request need not persist. Namely, there is an authorization that requires user consent but does not require persist.

What do you think about it?

This is good point. If I understand correctly, it will be needed to NOT persist such request as for example user consented the consent like:

Send the payment of 125 EUR

But then client may want to send another payment of the same value (EG. It is periodic monthly payment of 125 EUR) and hence it is required that user will need to consent this again as it is different payment.

IMO we need to have some logic in the policies to cover use-cases like this. So the policy itself needs to be smart-enough to understand this and decide if this requires consent or not.

I think we need to log an authorization, namely which client sends which authorization request and who authorized this request when, regardless of whether the authorization persists or not. I'm not sure this document should cover such logging but there is the case that such log can be used for audit.

I suppose we may use event SPI. This will automatically cover logging (as Keycloak has JBossLoggingEventListenerProvider) as well as other ways of auditing according to administrator preference (EG. Keycloak has a way to persist events in the DB to have a way they can be easily queried). There is already event detail logged specifying if user granted consent or if consent was automatically granted. We can possibly introduce other event details to cover also RAR data etc.

mposolda avatar Dec 17 '21 11:12 mposolda

@dgozalo Thank you for your update. I've read your update and confirmed that points I've commented on have been incorporated.

I have 2 comments on that.

I think there is an authorization that can be used as one shot. For example,

{
   "type": "payment_initiation",
   "locations": [
       "https://example.com/payments"
   ],
   "instructedAmount": {
       "currency": "EUR",
       "amount": "125.00"
   }
}

When a client sends this authorization request to an authorization server, it gets a consent from a user and sends an access token to the client.

After that, when the client sends the same authorization request to the authorization server, it again gets a consent from a resource owner because this payment initiation is different from the former one.

Considering this point, IMO, such the request need not persist. Namely, there is an authorization that requires user consent but does not require persist.

What do you think about it?

I think we need to log an authorization, namely which client sends which authorization request and who authorized this request when, regardless of whether the authorization persists or not. I'm not sure this document should cover such logging but there is the case that such log can be used for audit.

That's a very interesting point. The fact that a RAR payload is the same as a previously consented one doesn't mean that's strictly the same.

In the context of a bank, even if a transfer happens again with the same amount, it doesn't mean that I'll consent it this time.

When we get to the point of creating the configuration options for the RAR handlers, maybe an option like alwaysRequestsConsent would be sensible? It may also be possible to point to a specific part of the payload and request only if that changes, i.e:

 {
    "type": "payment_initiation",
    "locations": [
        "https://example.com/payments"
    ],
    "transactionId": 123123, <--- If this changes, we ask for consent.
    "instructedAmount": {
        "currency": "EUR",
        "amount": "125.00"
    }
 }

dgozalo avatar Dec 17 '21 11:12 dgozalo

@mposolda @tnorimat @dgozalo In general, I think this design kind of overlaps with what we have in authorization services and future plans to enable fine-grained specification of access.

For me, looks like we can leverage some key concepts from authorization services and come up with a simpler and general-purpose authorization API.

A gist of what I have in mind is:

Authorizer.evaluate(new ScopePermission("foo")).accept((result, cause) -> System.out.println(result.getGranted()));

Basically, we have an entry point, the Authorizer (which can be a provider), a Permission which can be extended to enable different types of permissions (scope, fine-grained/rich permissions), and an Evaluator that decides how permissions are evaluated/processed (for instance, sync/async).

public class ScopePermission extends KeycloakPermission {
    public ScopePermission(String type) {
        super("scope", type);
    }
}

public interface Evaluator {
    Result evaluate(Permission[] permissions, BiConsumer<Result, Throwable> handler);
}

Then you have the different Policy types which are registered in a PolicyRegistry and used to actually evaluate permissions:

public interface Policy {
    void evaluate(EvaluationContext context);
}

public interface EvaluationContext {
    List<Permission> getPermissions();
    void grant(Permission permission);

   // more methods here to obtain, for instance, contextual info about the subject or the transaction
}

For last, the client code would process the outcome by looking at the evaluation Result.

public interface Result {
    PermissionCollection getGranted();
}

Note that policies should be able to provide advice/obligations to the client code to perform certain actions with the result such as to show a consent page.

On top of that, we could be enabling other aspects such as:

  • Consent
  • Grant Management
  • User-defined policies for managing access to permissions (scopes, rar, etc)

pedroigor avatar Dec 20 '21 11:12 pedroigor

@mposolda @tnorimat @dgozalo In general, I think this design kind of overlaps with what we have in authorization services and future plans to enable fine-grained specification of access.

I would be greatly in favour of reusing as much as possible.

It would make sense to reuse the Policies Engine used in Authorization Services to evaluate any policies that we define in here.

Also, I like what you are suggesting about evaluating permissions, if others agree on this approach we should add it to the design.

This proposal is also defining how we would parse scopes and define a common authorization_details data structure that would contain the authorization data regardless of the origin, so if we can fit this authorization_details structure in existing mechanisms, then that would be great.

As @mposolda mentioned in another comment, it would simplify code quite a lot on SPIs because we'd work with a single object.

dgozalo avatar Dec 20 '21 13:12 dgozalo

@dgozalo Thank you for your update. I've read your update and confirmed that points I've commented on have been incorporated. I have 2 comments on that. I think there is an authorization that can be used as one shot. For example,

{
   "type": "payment_initiation",
   "locations": [
       "https://example.com/payments"
   ],
   "instructedAmount": {
       "currency": "EUR",
       "amount": "125.00"
   }
}

When a client sends this authorization request to an authorization server, it gets a consent from a user and sends an access token to the client. After that, when the client sends the same authorization request to the authorization server, it again gets a consent from a resource owner because this payment initiation is different from the former one. Considering this point, IMO, such the request need not persist. Namely, there is an authorization that requires user consent but does not require persist. What do you think about it? I think we need to log an authorization, namely which client sends which authorization request and who authorized this request when, regardless of whether the authorization persists or not. I'm not sure this document should cover such logging but there is the case that such log can be used for audit.

That's a very interesting point. The fact that a RAR payload is the same as a previously consented one doesn't mean that's strictly the same.

In the context of a bank, even if a transfer happens again with the same amount, it doesn't mean that I'll consent it this time.

When we get to the point of creating the configuration options for the RAR handlers, maybe an option like alwaysRequestsConsent would be sensible? It may also be possible to point to a specific part of the payload and request only if that changes, i.e:

 {
    "type": "payment_initiation",
    "locations": [
        "https://example.com/payments"
    ],
    "transactionId": 123123, <--- If this changes, we ask for consent.
    "instructedAmount": {
        "currency": "EUR",
        "amount": "125.00"
    }
 }

IMO, If an authorization includes unique id like transactionId in the actual payment services in the real world, alwaysRequestsConsent might be not needed.

tnorimat avatar Dec 23 '21 07:12 tnorimat