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

Marek Posolda mposolda at redhat.com
Tue Jan 29 06:34:56 EST 2019


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



More information about the keycloak-dev mailing list