Problems using Pac4j w/ Lagom (compile-time DI, PlayCookieSessionStore)

142 views
Skip to first unread message

Nicolas Rouquette

unread,
Mar 10, 2019, 10:32:03 PM3/10/19
to Pac4j users mailing list
I am trying to use Pac4j for a Lagom application that has a Play web front end with Keycloak.

It would be very helpful if there was a Pac4j version of the Lagom example secured with Keycloak: https://github.com/lagom/online-auction-scala
If anyone knows of something like this; please let me know.

I had used Pac4j before for a Play/Scala application w/ Keycloak and I didn't expect to experience difficulties w/ Lagom since it also uses Play.

With Lagom, there are 3 differences compared to Play/Scala (e.g., https://github.com/pac4j/play-pac4j-scala-demo).
I'm aware of the Pac4j lagom example (https://github.com/pac4j/lagom-pac4j); which is currently limited to rest services, not a complete play front end w/ backend services

So, I tried to build on that example for my use case. 
Compared to my earlier experience, this involves 2 significant changes;

1) Compile-time DI instead of Runtime DI.

Most of the Pac4J examples use Runtime DI w/ Guice whereas Lagom typically uses compile-time DI.
After experiencing both, I find compile-time DI much more useful because it forces to be explicit about everything that needs to be injected.

2) Using PlayCookieSessionStore

The doc mentions some differences between the two here: 
I am also using Scala instead of Java; this ought to be OK; however, I run sometimes into problems when
I try to override some behavior implemented in Pac4j somewhere. The problems are typically due to Java idioms
that result in loosing type information, for example: 

In Java, this line seems innocuous:

       final ProfileManager manager = getProfileManager(context);
       List<UserProfile> profiles = manager.getAll(loadProfilesFromSession);


With ProfileManager defined as:

              public class ProfileManager<U extends CommonProfile> { 
         ...
         public List<U> getAll(final boolean readFromSession) { ... }
         ...
       }

A direct translation of the Java code in Scala would result in a type error:

      val manager: ProfileManager[_ <: CommonProfile] = getProfileManager(context)
      val profiles: util.List[UserProfile] = manager.getAll(loadProfilesFromSession)

The Scala compiler detects a type error with the above; instead, we have to write:

      val manager: ProfileManager[_ <: CommonProfile] = getProfileManager(context)
      val profiles: util.List[_ <: CommonProfile] = manager.getAll(loadProfilesFromSession)

Beyond these Java/Scala type problems, there are a couple of bigger issues:

A) Pac4jScalaTemplateHelper is biased for PlayCacheSessionStore


Looking more closely, it seems a premature specialization; PlaySessionStore seems to be to be sufficient; 
it would have enabled using Pac4jScalaTemplateHelper w/ either variant: PlayCookieSessionStore or PlayCacheSessionStore.

B) Differences between PlayCacheSessionStore and PlayCookieSessionStore

There is some doc about these differences here: 

However, what is undocumented is the subtle behavior in this logic: 

Clearing sensitive data only happens in PlayCookieSessionStore; as far as I can tell, there is no equivalent behavior in PlayCacheSessionStore.
In PlayCookieSessionStore, clearing sensitive data effectively deletes the access_token, refresh_token but leaves the id_token:

Why is this done?

Without access & refresh tokens, it is effectively impossible to use Pac4j to invoke Pac4j-secured routes (e.g., in Play) or retrieve the access token
to pass it as an "Authentication: Bearer <token>" header for invoking Pac4j-secured REST endpoints like the lagom-pac4j example.

I tried to disable this clearing of sensitive data but this results in an even more puzzling behavior:

- Initially, there is no Play session, no authentication; so, when navigating to a Pac4j-secured web page, DefaultSecurityLogic requests authentication.
- When DefaultCallbackLogic is invoked after the authorization succeeds, it somehow ends up triggering the DefaultSecurityLogic on a different thread which has no context.
- No context means no profiles means back to square one!
- Eventually, Keycloak puts a stop to this cycle of authorization.

When clearing of sensitive data is enabled (as is done by default), the behavior is different:

- Initially, there is no Play session, no authentication; so, when navigating to a Pac4j-secured web page, DefaultSecurityLogic requests authentication.
- When DefaultCallbackLogic is invoked after the authorization succeeds, it somehow ends up triggering the DefaultSecurityLogic on a the same thread which has maintained the same context.
- This time, the context has profiles except that with sensitive data cleared, there's no access nor refresh token.

I am really baffled why clearing sensitive data has such a big difference in behavior.

In particular, why is the redirection at the end of the callback logic happens on the same thread when it's cleared vs. a different thread when it isn't?

Of course, it would be easier if I had done this by modifying the lagom example.
However, I can honestly say that I'm very surprised that adding pac4j to a working lagom application would end up being so strange and complicated to understand.

- Nicolas.

Jérôme LELEU

unread,
Mar 11, 2019, 5:17:02 AM3/11/19
to Nicolas Rouquette, Pac4j users mailing list
Hi,

Thanks for your feedback. There are many things reported here.

Lagom only targets REST APIs, I guess play-pac4j is the right solution if you target the UI as well. About compile-time/runtime DI as well as other design points, Sergey (@ihostage) should give you better answers than me.

About the PlayCookieSessionStore, I can help you:
I updated the documentation.
There is an issue with the template helper, it's known: https://github.com/pac4j/play-pac4j/issues/257 The fix should not be too complicated. I expected the reporter to work on that, but I'll eventually fix that.
With that session store, profiles are saved in a cookie so if the data is too big, the cookie is not created and you are never authenticated. When clearing data, the cookie is properly saved, but of course, some information have been removed.
The clearUserProfiles method is protected and thus made to be overriden for your specific behavior.

In any case, contributions and suggestions are always welcome. Feel free to submit PRs for changes.

Thanks.
Best regards,
Jérôme




--
You received this message because you are subscribed to the Google Groups "Pac4j users mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pac4j-users...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Nicolas Rouquette

unread,
Mar 11, 2019, 12:12:59 PM3/11/19
to Pac4j users mailing list
Thanks Jérôme for your reply.

Ah... the cookie size limitation! 

Now I understand why there is the method clearUserProfiles
Thanks for updating the documentation.

In my test, even leaving the access token is enough to exceed the cookie size limitation from the browser.
I switched to the PlayCacheSessionStore and now security works as intended.

- Nicolas.

cs...@cksworks.com

unread,
Mar 25, 2019, 1:41:27 PM3/25/19
to Pac4j users mailing list
I'm also in the process of getting play-pac4j to work with the API gateway in Lagom (scala). I'm essentially duplicating what is in the sample app SecurityModule#config() logic in a Pac4jComponents trait. I got things to start. But, when I hit the 'callback' controller, things start falling apart:

[ERROR] [03/23/2019 18:46:39.180] [portalApiGateway-internal-dev-mode-akka.actor.default-dispatcher-29] [akka.actor.ActorSystemImpl(portalApiGateway-internal-dev-mode)] Internal server error, sending 500 response
java.lang.RuntimeException: AkkaHttpServer doesn't handle Handlers of this type: play.core.routing.HandlerInvokerFactory$JavaActionInvokerFactory$$anon$3$$anon$4@36cfe277

Still figuring this. Happy to see there are others on the same path. Will update and share notes as progresses are made.

Thanks.
kc
Reply all
Reply to author
Forward
0 new messages