[keycloak-dev] Better support for "scope" in adapters
Pedro Igor Silva
psilva at redhat.com
Thu Jan 31 08:07:38 EST 2019
This what I like most about client scopes (in addition to all mapping you
do to claims in the tokens) :) Would also make sense to do the same thing
to client scopes ? So clients requesting "foo" would also get "bar" and
"xpto", for instance ?
Maybe this could avoid the client to request 10 scopes but just a more
coarse-grained scope representing all of them.
On Thu, Jan 31, 2019 at 10:43 AM Marek Posolda <mposolda at redhat.com> wrote:
> On 30/01/2019 14:40, Pedro Igor Silva wrote:
>
>
>
> On Wed, Jan 30, 2019 at 5:25 AM Marek Posolda <mposolda at redhat.com> wrote:
>
>> On 29/01/2019 19:49, Pedro Igor Silva wrote:
>>
>> I'm not sure if we need to consider that in our adapters.
>>
>> Usually, the front-end knows the set of scopes that it needs to interact
>> with the backend and stay functional.
>>
>> Maybe. I am personally not sure how people expect to use "scope"
>> parameters in their frontend applications. Maybe 90% of frontend clients
>> don't need to use "scope" parameter at all. And from the remaining, they
>> will be fine with the current support of the "scope" parameter.
>>
> I would say so, mainly because I think people are still using RBAC to
> enforce access to APIs. Enterprise scenarios don't really use scopes as
> they are more related with delegation.
>
> Yeah, maybe. Just a note that our client scopes support also allows to
> limit roles in the token. For example you can define role scope mappings of
> client scope "service1" to have role "service1.my-service1-role" . So by
> requesting "scope=service1", you will also receive this role in the token
> and hence can be used for RBAC based authorization.
>
> But anyway, I probably won't create any JIRAs for now. Will wait if there
> is some more feedback or some more requests for better support of "scope"
> parameter in the adapters.
>
> Thanks for the feedback Pedro!
>
> Marek
>
> One possibility, where I can see usage of this, is when frontend client
>> wants to invoke multiple different services and he wants to use different
>> access tokens with properly "isolated" audiences. So for example you want
>> to have:
>>
>> - access token with "scope=service1", which will have in itself audience
>> claim "aud: service1" and you will use it to invoke backend service1
>> - access token with "scope=service2", which will have in itself audience
>> claim "aud: service2" and you will use it to invoke backend service2
>>
>> In this case, having the possibility for adapters to "cache" multiple
>> tokens for various scopes can be beneficial IMO, so client can easily
>> retrieve proper access token based on the service he wants to invoke.
>>
>> And the backend by itself is free to exchange tokens to call other
>> services (service chaining).
>>
>> Don't think that brings a lot of complexity to the front-end and,
> probably, indicates a bad design?
>
>> IMO in many cases, you're right. For example when frontend client uses
>> access token to invoke backend "service1", this backend may want to
>> retrieve some other data from "service11". So service1 backend needs to
>> reuse the token or he wants to exchange this token.
>>
>> But in many cases, you want to avoid this. So in my example above, when
>> you have access token with "aud: service1", you want this access token to
>> be used solely to invoke service1. You don't want to have one huge access
>> token, which will have all the audiences like:
>>
>> aud: [ "service1", "service2" ]
>>
> The access token is also tied with the client, what means "this client is
> allowed to invoke service1 and service2". I usually don't see a problem on
> that if you consider that multiple audiences mean that a high degree of
> trust between the parties involved. What I think is true for most
> enterprise use cases where the front-end is basically accessing internal
> services.
>
> It is also worthy to consider, IMO, that the fact that you have distinct
> services, does not mean they are not part of the same API, thus the same
> audience.
>
>> You may want separate access tokens with isolated audiences exactly
>> because you don't want service1 and service2 to be able to invoke each
>> other. IMO this isolation is one of the main usages of the "aud" claim in
>> the tokens.
>>
>>
>> One thing that makes sense though is "incremental authorization" and
>> allow apps to request additional scopes during an authentication session,
>> so the app gets what was previously granted and the new scopes (depending
>> on user consent). But I think we support that already, right ?
>>
>> We don't support it directly and maybe this is something to improve. ATM
>> you will need programatically do something like this:
>>
>> String existingScope = existingAccessToken.getScope();
>>
>> // I want to use existingScope and add "phone" scope to it
>> String newScope = existingScope + " phone";
>>
>> // Request the login for new scope now
>>
>>
>> The part of "requesting login for new scope" is possible with javascript
>> adapter, but not easily with the "java" adapter. With java adapter, there
>> is no easy way to manually "build" URL to sent to OIDC authentication
>> endpoint (equivalent of keycloak.js method "createLoginUrl"). That's also
>> one of the things, which I proposed to improve in this email thread.
>>
> Marek
>>
>>
>> Regards.
>> Pedro Igor
>>
>> On Tue, Jan 29, 2019 at 9:36 AM Marek Posolda <mposolda at redhat.com>
>> wrote:
>>
>>> During my work on Client Scopes last year, I did not any work on the
>>> adapters side. I think there is a space for improvement here. I will try
>>> to summary current issues and some initial proposals for improve things.
>>> Suggestions welcomed! And sorry for longer email :)
>>>
>>>
>>> Both javascript adapter and servlet adapter has some way for requesting
>>> the additional "scope" and ensure that that initial OIDC authentication
>>> request sent to Keycloak will contain some custom "scope" parameter. The
>>> javascript adapter has support for "scope" as an option of the "login"
>>> method [1]. The servlet adapter has a possibility to inject custom
>>> "scope" with parameters forwarding [2]. I am not sure about node.js and
>>> other adapters TBH. But even for javascript and servlet adapter, the
>>> current support is quite limited for few reasons. For example:
>>>
>>> - The approach of parameters forwarding used in servlet adapters
>>> requires to logout before requesting the additional scope. Because when
>>> I am already authenticated in the application and I open secured URL
>>> like http://localhost/app/secured?scope=some-custom-scope, the adapter
>>> will just ignore it in case that user is already logged-in and it will
>>> automatically forward to the application.
>>>
>>> - Both servlet and javascript adapters support to have just single
>>> "triplet" of tokens per browser session. In this context "triplet" means
>>> the single set of 3 tokens (ID token , Access Token , Refresh token). So
>>> for example when I want to request the custom scope for being able to
>>> invoke "service1", I can use "scope=service1". However after Keycloak
>>> redirects me back to the application, the existing triplet of tokens is
>>> just replaced with the new one for "service1" . Then when I want to
>>> later invoke another service like "service2", I need to request the
>>> additional scope "scope=service2", which will replace my tokens on the
>>> adapter's side with the "service2" tokens . But then later when I want
>>> to switch again to "service1", I need to redirect to Keycloak again as
>>> the current triplet of tokens for "service1" etc.
>>>
>>>
>>> To improve this limitation, I think that it will be good if adapters
>>> easily support the following:
>>>
>>> - Instead of having single triplet of tokens, it will be good if
>>> adapters can contain Map of tokens. Key of the map can be "scope"
>>> parameter. So for example, the adapter will have "default" tokens
>>> (those, which were used for initial login), the tokens for "service1"
>>> and the tokens for "service2" .
>>>
>>> - It will be nice if there is easy way to ask adapter for "service1"
>>> scope. In case that I don't have yet this scope, adapter will redirect
>>> me to Keycloak with "scope=service1". If I already have it, adapter will
>>> return me an existing token. If existing access token is expired,
>>> adapter will refresh the access token for requested scope in the
>>> background and return me the "updated" token.
>>>
>>> - When I want to invoke service1 and I need to use "scope=service1", I
>>> usually need just access token and refresh token. I don't need ID Token
>>> anymore. I also don't need the "profile" and "email" claims to be
>>> returned in the new access token. This is related to the JIRA of having
>>> the server-side support for client scopes like (always, default,
>>> optional) instead of current (default, optional) [3]. In other words,
>>> the client scopes "profile" and "email" will be default client scopes,
>>> which means that if I don't use "scope=openid" in the OIDC initial
>>> request, the "profile" and "email" will be ommited from the response as
>>> well as the ID Token will be ommited from the response.
>>>
>>>
>>> So how to support this on adapters? For Keycloak.js, I can think about
>>> some variation of existing "update" method like this:
>>>
>>>
>>> keycloak.updateTokenWithScope('service1',
>>> 5).success(function(accessToken, refreshed) {
>>> if (refreshed) {
>>> alert("I had existing accessToken for scope 'service1', but
>>> it needed to be refreshed as it was expired or about to expire in less
>>> than 5 seconds");
>>> } else {
>>> alert('I have accessToken for 'service1', which didn't
>>> need to be refreshed');
>>> }
>>> // I can invoke REST service now with the accessToken
>>> ...
>>> }).error(function() {
>>> alert("Failed to refresh the token OR I don't have yet scope
>>> for 'service1' .");
>>> // User usually need to call keycloak.login with the requested
>>> scope now...
>>> });
>>>
>>>
>>>
>>>
>>> For servlet adapter something like:
>>>
>>> KeycloakSecurityContext ctx = ... // Retrieved from HttpServletRequest
>>> or Principal as currently
>>>
>>> if (ctx.hasScope("service1")) {
>>> try {
>>> String accessToken = ctx.getScope("service1");
>>> // Invoke service1 with accessToken now
>>> } catch (TokenRefreshException ex) {
>>> log.error("I already had scope for service1, but failed to
>>> refresh the token. Need to re-login for the scope service1");
>>>
>>> // See method below
>>> redirectToKeycloakWithService1Scope();
>>> }
>>> } else {
>>> // See method below
>>> redirectToKeycloakWithService1Scope();
>>> }
>>>
>>>
>>> private void redirectToKeycloakWithService1Scope() {
>>> KeycloakRedirectUriBuilder builder = ctx.createLoginUrl();
>>> URL url = builder.scope("service1").build();
>>> httpServletResponse.sendRedirect(url);
>>> }
>>>
>>>
>>> Regarding the class KeycloakRedirectUriBuilder, I was thinking about
>>> this class so that servlet adapter are able to easily create login URL
>>> with custom values for things like scope, prompt, max_age etc. This
>>> capability is currently missing in servlet adapters and the current
>>> approach based on parameters forwarding is a bit clunky for few reasons.
>>> One reason is usability and the other is, that you need to logout first.
>>>
>>>
>>> [1]
>>>
>>> https://www.keycloak.org/docs/latest/securing_apps/index.html#javascript-adapter-reference
>>> [2]
>>>
>>> https://www.keycloak.org/docs/latest/securing_apps/index.html#_params_forwarding
>>> [3] https://issues.jboss.org/browse/KEYCLOAK-8323
>>>
>>> Marek
>>>
>>> _______________________________________________
>>> keycloak-dev mailing list
>>> keycloak-dev at lists.jboss.org
>>> https://lists.jboss.org/mailman/listinfo/keycloak-dev
>>
>>
>>
>
More information about the keycloak-dev
mailing list