[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