[keycloak-dev] Better support for "scope" in adapters

Stian Thorgersen sthorger at redhat.com
Thu Feb 7 08:31:54 EST 2019


On Thu, 7 Feb 2019 at 09:40, Marek Posolda <mposolda at redhat.com> wrote:

> On 06/02/2019 15:26, Stian Thorgersen wrote:
>
>
>
> On Wed, 6 Feb 2019 at 11:53, Marek Posolda <mposolda at redhat.com> wrote:
>
>> On 05/02/2019 09:39, Stian Thorgersen wrote:
>>
>> Tried to filter out implementation details here, so may have lost some
>> details. It would be good if we can try to keep discussions at a higher
>> level at least initially as it makes it much easier to follow the
>> discussion.
>>
>> Point taken :) Will try to improve next time.
>>
>>
>> For scopes I can see the most common use-case will be the ability to do
>> incremental scopes. By that I mean the application doesn't request all
>> permissions it may need, but rather starts small and asks for more as the
>> user extends use of the application. This is mostly relevant to
>> applications that require consent.
>>
>> Now with regards to the application being able to have different tokens
>> to invoke different services I'm not convinced this is needed so we should
>> rather wait for demand here. There's two ways a single app can consume
>> multiple services:
>>
>> 1) Application directly invokes multiple services - in this case the
>> application should be able to use scopes or token exchange to obtain tokens
>> to invoke different services. In fact I'd say token exchange is probably
>> what is wanted here rather than scopes.
>> 2) Application invokes a backend service that aggregates multiple
>> services - in this case token exchange comes in as the backend service
>> needs to be able to obtain tokens to invoke the different services
>>
>> I would think option 2 is the best approach as it allows implementing the
>> complex code in server side code and also makes the application more
>> transparent to API changes.
>>
>> This seems to be inline with what Pedro mentioned as well. +1 for waiting
>> for a demand for this.
>>
>>
>> With regards to incremental scope support we need to be able to do that
>> without requiring logout. For JS adapter that should already work, but it
>> has one issue and that is you can't set the scope to use with automated
>> login. We should probably make the scope configurable in the init function
>> as well as when invoking login function directly. For servlet we should
>> probably also have a way to configure the initial scope and expose a method
>> to obtain additional scopes without requiring logout.
>>
>> +1
>>
>> Regarding JS adapter, there are few other things, which you can't do at
>> "init" method. For example adding the "prompt" . Maybe all the current
>> options of "login" method can be just used as arguments to "init" method?
>>
> Or perhaps it should be something like defaultLoginOptions which is an
> object that we simply pass to the login function?
>
> Was thinking about that as well :) It will be probably better as if
> someone wants to call "keycloak.login" and override just single thing (EG.
> scope), he can clone the defaultLoginOptions and override just scope.
>

Can you create a JIRA for it?


> For the servlet adapter, it will be probably good to have something like
>> JS adapter "keycloak.login" method or "keycloak.createLoginUrl", which
>> allows to add things like custom scope, prompt etc. I proposed something
>> like KeycloakLoginUrlBuilder, which will allow to easy add things like
>> "scope" or "prompt" in the adapters code.
>>
> Isn't really what you want is server-side action that returns the redirect
> rather than directly adding the login url to the page? The reason I'm
> saying that is that the adapter needs to generate the state param and such,
> which it can't do if the login url is just placed directly on the page.
>
> I did not mean to directly add URL to the page, but rather something along
> the lines of:
>
> KeycloakLoginRedirectURL loginRedirect = KeycloakLoginRedirectURLBuilder
>         .prompt("consent")
>         .scope(accessToken.getScope() + " phone")
>         .build();
>
> getKeycloakSecurityContext().sendRedirectToLogin(loginRedirect);
>
> Note that when you call "sendRedirectToLogin", it will the responsibility
> of the adapter to generate "state" and add "redirect_uri" from the
> keycloak.json config etc. Maybe KeycloakLoginRedirectURL is not the best
> name for this, rather something like "KeycloakLoginRedirect" or
> "KeycloakLoginRedirectState", but that's an implementation detail.
>

Got it. That makes sense. Can you create a JIRA?


> Marek
>
>
>> I'm not convinced about client scope inheritance. It has an additional
>> implementation complexity, but most importantly usability with regards to
>> understand what is actually included in the token when you have
>> inheritance. It may also have some strange side-effects like how do you
>> make sure the order of what is applied is correct. Again, probably
>> something best left until there is demand for it.
>>
>> We already have "priority" on protocolMappers, so ordering likely won't
>> be an issue. You can add protocolMappers of all the "effective" client
>> scopes and sort the protocol mappers. But agree that will be good to wait
>> for more demand before adding this.
>>
>> Marek
>>
>> On Thu, 31 Jan 2019 at 17:04, Pedro Igor Silva <psilva at redhat.com> wrote:
>>
>>> +1
>>>
>>> On Thu, Jan 31, 2019 at 11:14 AM Marek Posolda <mposolda at redhat.com>
>>> wrote:
>>>
>>> > On 31/01/2019 14:07, Pedro Igor Silva wrote:
>>> >
>>> > 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.
>>> >
>>> > There is opened JIRA for client scopes inheritance
>>> > https://issues.jboss.org/browse/KEYCLOAK-6633 . I believe this will
>>> cover
>>> > what you have in mind? It's just not yet done...
>>> >
>>> > Marek
>>> >
>>> >
>>> >
>>> > 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
>>> >>>
>>> >>>
>>> >>>
>>> >>
>>> >
>>> _______________________________________________
>>> 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