keycloak-community
keycloak-community copied to clipboard
Authorization Handler / Client Scopes enhancement for Dynamic Scopes / RAR
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.
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 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.
@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.
@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"
}
}
@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)
@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 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.