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

Marek Posolda mposolda at redhat.com
Thu Feb 7 13:13:14 EST 2019


On 07/02/2019 14:31, Stian Thorgersen wrote:
>
>
> On Thu, 7 Feb 2019 at 09:40, Marek Posolda <mposolda at redhat.com 
> <mailto: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
>>     <mailto: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?
Created https://issues.jboss.org/browse/KEYCLOAK-9519
>
>>         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?

Created https://issues.jboss.org/browse/KEYCLOAK-9520

Marek

>     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 <mailto:psilva at redhat.com>> wrote:
>>>
>>>             +1
>>>
>>>             On Thu, Jan 31, 2019 at 11:14 AM Marek Posolda
>>>             <mposolda at redhat.com <mailto: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 <mailto: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 <mailto: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 <mailto: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
>>>             <mailto:keycloak-dev at lists.jboss.org>
>>>             >>>> https://lists.jboss.org/mailman/listinfo/keycloak-dev
>>>             >>>
>>>             >>>
>>>             >>>
>>>             >>
>>>             >
>>>             _______________________________________________
>>>             keycloak-dev mailing list
>>>             keycloak-dev at lists.jboss.org
>>>             <mailto:keycloak-dev at lists.jboss.org>
>>>             https://lists.jboss.org/mailman/listinfo/keycloak-dev
>>>
>>
>



More information about the keycloak-dev mailing list