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

Marek Posolda mposolda at redhat.com
Thu Jan 31 08:14:53 EST 2019


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



More information about the keycloak-dev mailing list