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

Marek Posolda mposolda at redhat.com
Thu Feb 7 03:40:41 EST 2019


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.
>
>     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.

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