[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