pure AJAX API

251 views
Skip to first unread message

Élie Roux

unread,
Jun 15, 2017, 7:54:03 AM6/15/17
to Pac4j users mailing list
Hello,

I'm quite new to pac4j and I'm really lost trying to do a very simple thing. First, here is a very short description of what I'm trying to achieve:

- a pure API server (no html page)
- allowing to handle different login services (password, oauth2, saml, etc.)
- sending back a jwt token to the user, that will then use this token in other places (different servers, programming languages, etc.)

I'm trying to implement that in jax-rs, I have some boilerplate code on https://github.com/BuddhistDigitalResourceCenter/auth-api but I cannot find any doc nor simple example on the following first problem I'm trying to solve: suppose I'm getting a user and password from an API point, jersey gives me these values as two Strings, how can I check if they are valid or invalid? Is there a way to do that with pac4j? Or if not, maybe pac4j is not intended for this kind of use case? If so, do you know any library that would help?

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 15, 2017, 9:14:00 AM6/15/17
to Pac4j users mailing list
It should work ok with pac4j, but it is true that it has been at first thought for non-REST APIs IMHO.

For example, in my application (based on dropwizard: https://gitlab.com/linagora/petals-cockpit), the backend provides an API to send a user/pass and it will setup an http session in return.
Your case is similar but it should return a jwt token instead :)

Normally you should setup pac4j and jax-rs so that:
 - a request on the API point is taken into account by pac4j with a first Client your own CredentialsExtractor (for example mine: https://gitlab.com/linagora/petals-cockpit/blob/master/backend/src/main/java/org/ow2/petals/cockpit/server/security/CockpitExtractor.java) and depending on your need an adequate Authenticator (that will check if the user/pass are valid thus).
 - if it's valid, you will need to generate a Jwt token, I think manually (or at least, pac4j won't do it for you, but may provide the service that will generate it)
 - if needed, a request on the rest of the API points is taken into account by pac4j with another Client that authenticate the request based on the Jwt token using a Jws Authenticator

By the way, I will answer you on the matter of setting up pac4j with jax-rs very soon (in the issue on jax-rs-pac4j :), but I think your question is very symptomatic of the kind of things that are not so clear to handle with pac4j. So it is a good thing you are facing it so that we can find the best way to tackle it :)

Victor

Élie Roux

unread,
Jun 15, 2017, 11:25:18 AM6/15/17
to victo...@gmail.com, Pac4j users mailing list
Hello,

Thank you very much for your quick answer!

> For example, in my application (based on dropwizard:
> https://gitlab.com/linagora/petals-cockpit), the backend provides an
> API to send a user/pass and it will setup an http session in return.
> Your case is similar but it should return a jwt token instead :)

Indeed!

> Normally you should setup pac4j and jax-rs so that: - a request on
> the API point is taken into account by pac4j with a first Client your
> own CredentialsExtractor and depending on your need an adequate
> Authenticator (that will check if the user/pass are valid thus).

Well, sorry but I'm already lost... there seems to be some local jargon
for which I cannot find any definition in the doc: what is an extractor?
and how can I build an authenticator object?

In your code I see

defaultAuthenticator(new CockpitAuthenticator());

so I guess I have to use something similar with the CouchDB
authenticator. But looking at the doc for CouchDB (or any other
authenticator), I see no clue about the defaultAuthenticator function,
nor any reference to an Authenticator object... This is very obscure to me!

> - if it's valid, you will need to generate a Jwt token, I think
> manually (or at least, pac4j won't do it for you, but may provide
> the service that will generate it)

This shouldn't be a problem indeed

> - if needed, a request on the rest of the API points is taken into
> account by pac4j with another Client that authenticate the request
> based on the Jwt token using a Jws Authenticator

If I manage to make a basic enpoint work, this one shouldn't be too long
to produce I guess...

> By the way, I will answer you on the matter of setting up pac4j with
> jax-rs very soon (in the issue on jax-rs-pac4j :), but I think your
> question is very symptomatic of the kind of things that are not so
> clear to handle with pac4j. So it is a good thing you are facing it
> so that we can find the best way to tackle it :)

Glad this is useful!

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 15, 2017, 11:37:08 AM6/15/17
to Pac4j users mailing list, victo...@gmail.com

> Normally you should setup pac4j and jax-rs so that: - a request on
> the API point is taken into account by pac4j with a first Client your
> own CredentialsExtractor and depending on your need an adequate
> Authenticator (that will check if the user/pass are valid thus).

Well, sorry but I'm already lost... there seems to be some local jargon
for which I cannot find any definition in the doc: what is an extractor?
and how can I build an authenticator object?

In your code I see

defaultAuthenticator(new CockpitAuthenticator());

so I guess I have to use something similar with the CouchDB
authenticator. But looking at the doc for CouchDB (or any other
authenticator), I see no clue about the defaultAuthenticator function,
nor any reference to an Authenticator object... This is very obscure to me!

The specificity with your situation (and mine), is that we are in the need of a specific pac4j (indirect) client for which the behaviour is to extract some data from the body of the message. You could actually, if you are ok to use, e.g. a more typical Form POST or basic auth from the pov of the API caller, use the pac4j HTTP client (http://www.pac4j.org/2.0.x/docs/clients/http.html) and not bother with this extractor thing.
If you need to extract the data from the body and it is in a proprietary format (in my case it is a simple json with user and password), then you need to define your own client (like this: https://gitlab.com/linagora/petals-cockpit/blob/master/backend/src/main/java/org/ow2/petals/cockpit/server/security/CockpitAuthClient.java) and make your own extractor which role is to extract the user/pass (or any type of credential) from the body :)

So maybe a simpler way to handle your situation is to use one of the indirect HTTP client (depending on how you are ready to constrain the API caller) and configure it with the couchdb authenticator (or make your own if needed).

This client will be registered in the pac4j configuration, and then you will be able to, with jax-rs-pac4j, use one of the annotations on the resource method definition (in the resource class) to bind it to the method.
Basically, if the method is executed, it means the authentication (with respect to the authenticator) worked and then you can access the profile to generate a Jwt token and return it for example.
Something like that (there may be more to it ^^).
 

> - if needed, a request on the rest of the API points is taken into
> account by pac4j with another Client that authenticate the request
> based on the Jwt token using a Jws Authenticator

If I manage to make a basic enpoint work, this one shouldn't be too long
to produce I guess...

For this, you would usually setup a direct header-based http client coupled with a jwt authenticator that will take care of validating the jwt token and generate the profile :)

Élie Roux

unread,
Jun 15, 2017, 12:49:34 PM6/15/17
to victo...@gmail.com, Pac4j users mailing list
> The specificity with your situation (and mine), is that we are in
> the need of a specific pac4j (indirect) client for which the
> behaviour is to extract some data from the body of the message. You
> could actually, if you are ok to use, e.g. a more typical Form POST
> or basic auth from the pov of the API caller, use the pac4j HTTP
> client (http://www.pac4j.org/2.0.x/docs/clients/http.html) and not
> bother with this extractor thing.

Well, I think I'd like to do things in a clean manner, so let's try this
extractor mechanism, I believe it's the best option

> If you need to extract the data from the body and it is in a
> proprietary format (in my case it is a simple json with user and
> password),

That's what I'll have too!

> then you need to define your own client (like this:
> https://gitlab.com/linagora/petals-cockpit/blob/master/backend/src/main/java/org/ow2/petals/cockpit/server/security/CockpitAuthClient.java)
>
>
and make your own extractor which role is to extract the user/pass (or
> any type of credential) from the body :)

Here is where I'm quite lost in fact... maybe because I'm not used
enough to Jersey: where in your code do you define the mapping between
the path ("/login" for instance) and the extractor? where is this
plumbing done?

> So maybe a simpler way to handle your situation is to use one of the
> indirect HTTP client (depending on how you are ready to constrain
> the API caller) and configure it with the couchdb authenticator (or
> make your own if needed).

This is also something I don't understand: is there a doc somewhere to
build an Authenticator object from a CouchProfileService? (or any
ProfileService?)

> This client will be registered in the pac4j configuration, and then
> you will be able to, with jax-rs-pac4j, use one of the annotations on
> the resource method definition (in the resource class) to bind it to
> the method. Basically, if the method is executed, it means the
> authentication (with respect to the authenticator) worked and then
> you can access the profile to generate a Jwt token and return it for
> example. Something like that (there may be more to it ^^).

That sounds good!

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 15, 2017, 1:06:35 PM6/15/17
to Pac4j users mailing list, victo...@gmail.com


Le jeudi 15 juin 2017 18:49:34 UTC+2, Élie Roux a écrit :
> The specificity with your situation (and mine), is that we are in
> the need of a specific pac4j (indirect) client for which the
> behaviour is to extract some data from the body of the message. You
> could actually, if you are ok to use, e.g. a more typical Form POST
> or basic auth from the pov of the API caller, use the pac4j HTTP
> client (http://www.pac4j.org/2.0.x/docs/clients/http.html) and not
> bother with this extractor thing.

Well, I think I'd like to do things in a clean manner, so let's try this
extractor mechanism, I believe it's the best option

For now I've used basic auth to examplify a solution below

> then you need to define your own client (like this:
> https://gitlab.com/linagora/petals-cockpit/blob/master/backend/src/main/java/org/ow2/petals/cockpit/server/security/CockpitAuthClient.java)
>
>
and make your own extractor which role is to extract the user/pass (or
> any type of credential) from the body :)

Here is where I'm quite lost in fact... maybe because I'm not used
enough to Jersey: where in your code do you define the mapping between
the path ("/login" for instance) and the extractor? where is this
plumbing done?

> So maybe a simpler way to handle your situation is to use one of the
> indirect HTTP client (depending on how you are ready to constrain
> the API caller) and configure it with the couchdb authenticator (or
> make your own if needed).

This is also something I don't understand: is there a doc somewhere to
build an Authenticator object from a CouchProfileService? (or any
ProfileService?)

CouchProfileService IS an Authenticator in this case :)
 

> This client will be registered in the pac4j configuration, and then
> you will be able to, with jax-rs-pac4j, use one of the annotations on
> the resource method definition (in the resource class) to bind it to
> the method. Basically, if the method is executed, it means the
> authentication (with respect to the authenticator) worked and then
> you can access the profile to generate a Jwt token and return it for
> example. Something like that (there may be more to it ^^).

That sounds good!

So here is an example for your use case:

Since you are using autoscanning of the classpath, I decided to introduce a class for configuring pac4j :

package io.bdrc.auth.server;

import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;

import org.pac4j.core.config.Config;
import org.pac4j.couch.profile.service.CouchProfileService;
import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
import org.pac4j.jax.rs.features.JaxRsConfigProvider;
import org.pac4j.jax.rs.features.Pac4JSecurityFeature;
import org.pac4j.jax.rs.jersey.features.Pac4JValueFactoryProvider;
import org.pac4j.jax.rs.servlet.features.ServletJaxRsContextFactoryProvider;

@Provider
public class MySecurityFeature implements Feature {

   
@Override
   
public boolean configure(FeatureContext context) {
        context
           
.register(new JaxRsConfigProvider(buildConfig()))
           
.register(new Pac4JSecurityFeature())
           
.register(new Pac4JValueFactoryProvider.Binder())
           
.register(new ServletJaxRsContextFactoryProvider());

       
return true;
   
}

   
private Config buildConfig() {
       
Config config = new Config();
       
       
CouchProfileService couch = new CouchProfileService();
       
// TODO configure couch...
       
IndirectBasicAuthClient loginClient = new IndirectBasicAuthClient(couch);
        config
.getClients().getClients().add(loginClient);
        config
.getClients().setDefaultClient(loginClient);
       
       
return config;
   
}
}

Now, it should be automatically registered by jersey on startup (this was documented here: https://github.com/pac4j/jax-rs-pac4j#jax-rs-autoscanning)

Then you need to configure some bindings from url to your client. In this case, we need an indirect client because we want to do some authentication directly and only that (not redirect or do a one-time authentication for a resource access).

package io.bdrc.auth.server;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.pac4j.couch.profile.CouchProfile;
import org.pac4j.jax.rs.annotations.Pac4JCallback;
import org.pac4j.jax.rs.annotations.Pac4JProfile;

@Path("login")
public class MyAuthResource {

   
@POST
   
@Pac4JCallback(skipResponse = true)
   
@Produces(MediaType.TEXT_PLAIN)
   
public String auth(@Pac4JProfile CouchProfile p) {
       
// pretty sure there is a better way to return it :)
       
return "generated token based on " + p.getId();
   
}
}

skipResponse means that if the authentication succeed (i.e. the authenticator has been able, based on the basic headers of the http request on /login, to authenticate the user against the couchdb db), then instead of having pac4j answers with a redirection, pac4j will simply let jersey take over and the method will be executed.
Then you can answer with the token as you desire (maybe wrapping it in json).

Also note that most certainly, you will have a second client for the Jwt auth that would be bound to other URLs (there is way to make it apply to a set of urls, or to all url except some, etc) and this client will use some jwt service that you should reuse in order to generate the jwt here... not exactly sure how you would do that with pure jax-rs but shouldn't be too hard :)

Note also I didn't test this, it is some ideas to start making it work, don't hesitate to ask more questions :)


 

Thank you!
--
Elie

Élie Roux

unread,
Jun 15, 2017, 2:02:08 PM6/15/17
to victo...@gmail.com, Pac4j users mailing list
In fact what I find disturbing is maybe just the vocabulary, I don't
particularly want to "protect" /login, I want to make it a login
endpoint... but I guess I just need to be used to this vocabulary...
generally speaking, I'm really feeling I'm using pac4j in a way that was
not intended, but pac4j is evolving too I guess!

> CouchProfileService IS an Authenticator in this case :)

Oooooh, that's enlightening, I think that was the main missing piece!

> So here is an example for your use case:

I'll test it tomorrow, but I have a few comments

> @POST @Pac4JCallback(skipResponse =true)
> @Produces(MediaType.TEXT_PLAIN)
> publicStringauth(@Pac4JProfileCouchProfilep){ // pretty sure there is
> a better way to return it :) return"generated token based on
> "+p.getId(); }

Another big puzzlement I have is: what happens if the login/password is
wrong? Does it send a 401 response? Can I control the payload?

> skipResponse means that if the authentication succeed (i.e. the
> authenticator has been able, based on the basic headers of the http
> request on /login, to authenticate the user against the couchdb db),
> then instead of having pac4j answers with a redirection, pac4j will
> simply let jersey take over and the method will be executed. Then you
> can answer with the token as you desire (maybe wrapping it in json).

Ok, that's what I want to do indeed!

> Also note that most certainly, you will have a second client for the
> Jwt auth that would be bound to other URLs (there is way to make it
> apply to a set of urls, or to all url except some, etc) and this
> client will use some jwt service that you should reuse in order to
> generate the jwt here... not exactly sure how you would do that with
> pure jax-rs but shouldn't be too hard :)

I think once I'll have one endpoint working, it shouldn't be too hard
indeed...

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 16, 2017, 3:38:41 AM6/16/17
to Pac4j users mailing list, victo...@gmail.com


Le jeudi 15 juin 2017 20:02:08 UTC+2, Élie Roux a écrit :
> This is explained here:
> https://github.com/pac4j/jax-rs-pac4j#3-protect-urls-securityfilter
> and below

In fact what I find disturbing is maybe just the vocabulary, I don't
particularly want to "protect" /login, I want to make it a login
endpoint... but I guess I just need to be used to this vocabulary...
generally speaking, I'm really feeling I'm using pac4j in a way that was
not intended, but pac4j is evolving too I guess!

Actually, /login is not protected, I may have used the wrong term.
To protect an URL, with jax-rs-pac4j, you annotate it with @Pac4JSecurity (or setup a filter that will cover a set of urls).
With @Pac4JCallback, you setup an authentication endpoint actually.
The term is not very nice because in our case, this is not really a callback (normally, an unauthenticated browser client would get redirected from a protected url and then would call back the callback url to finish the authentication).

I share your feeling about using it in a way that was not intended, but that only concerns the authentication (in the sense of going from a login/pass to a jwt token for you, and a login/pass to an http session for me).
Actually, you could even decide not to use pac4j at all to generate the jwt token, it is just that since it contains some classes to link to the db, and handle error case automagically, it is easier (or more mysterious ;).

The rest will be smooth: you will setup direct clients on url to protect and you will use it as expected. 

> @POST @Pac4JCallback(skipResponse =true)
> @Produces(MediaType.TEXT_PLAIN)
> publicStringauth(@Pac4JProfileCouchProfilep){ // pretty sure there is
> a better way to return it :) return"generated token based on
> "+p.getId(); }

Another big puzzlement I have is: what happens if the login/password is
wrong? Does it send a 401 response? Can I control the payload?

Yes it does send a 401 response and yes you can control the payload, but it will feel like a hack too, as before.
There is room for improvement I guess on that side too :)

Élie Roux

unread,
Jun 16, 2017, 6:04:58 AM6/16/17
to victo...@gmail.com, Pac4j users mailing list
Hello,

I have read and understood your code, thank you very much!

A few questions though:

This is not really a question but I'll just write something I think I've
undestood without being sure: in your first file defining the feature,
we define the default client. Then what triggers it in our auth method
is @Pac4JCallback, is that correct? If we wanted another client, like
jwt, we would pass the clients argument to the annotation?

Now, I'm still blocked, this time by test configuration. Basically I'm
mocking a couchdb server for testing purpose, so I need to configure
CouchProfileService with different values according to the environment.
And as I'll certainly have other variations, what I guess I want is to
create a different Config object in my testing environment. But I'm
really lost on the way to do that. My undestanding is that it's more a
jax-rs question than a pac4j question, but I actually have really hard
time finding any sort of documentation or example of that... can you
give me some clues?

> I share your feeling about using it in a way that was not intended,
> but that only concerns the authentication (in the sense of going from
> a login/pass to a jwt token for you, and a login/pass to an http
> session for me).

Indeed. I think the term "callback" is what's confusing, it's really
from a very specific point of view, I think a more general "endpoint"
would be less confusing and more generic...

> The rest will be smooth: you will setup direct clients on url to
> protect and you will use it as expected.

Yes, that's what's documented by pac4j

> Yes it does send a 401 response and yes you can control the payload,
> but it will feel like a hack too, as before. There is room for
> improvement I guess on that side too :)

Ok thank you very much! What is the way to control the payload in case
of an error?

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 16, 2017, 7:05:52 AM6/16/17
to Pac4j users mailing list, victo...@gmail.com


Le vendredi 16 juin 2017 12:04:58 UTC+2, Élie Roux a écrit :

This is not really a question but I'll just write something I think I've
undestood without being sure: in your first file defining the feature,
we define the default client. Then what triggers it in our auth method
is @Pac4JCallback, is that correct? If we wanted another client, like
jwt, we would pass the clients argument to the annotation?

Again, that's what is strange with the way we use it.
Normally, with pac4j, as I said before, the browser requests protected a resource, gets redirected to a login page (for example) and then the login page calls back the callback with the auth data, and finally the callback redirects the browser to the original page.
The trick in that is that when the browser gets redirected to the login page, it gets also the information about the client (that was protecting the resource) who trigerred the redirection, and thus it passes the client name as a parameter to the callback url!
That's how the callback knows which client is concerned.
In our case, since we don't want to constrain the caller to know about this concern, we set a default client, which will be used as default by the callback endpoint!

In your case, there is no reason for plugin a jwt client to the callback url! The jwt client will serve to protect urls, not do the authentication on the callback url. But if you wanted to do it, then you would need to pass the name of the client as a parameter to the callback url call!

Again, that's maybe why it could be simpler NOT to use the pac4j callback mechanism to do the primary authentication (from login/pass to jwt) but only the pac4j client to to the direct (i.e., for every call) authentication based on the jwt token.
That's a choice you have to make :)
 

Now, I'm still blocked, this time by test configuration. Basically I'm
mocking a couchdb server for testing purpose, so I need to configure
CouchProfileService with different values according to the environment.
And as I'll certainly have other variations, what I guess I want is to
create a different Config object in my testing environment. But I'm
really lost on the way to do that. My undestanding is that it's more a
jax-rs question than a pac4j question, but I actually have really hard
time finding any sort of documentation or example of that... can you
give me some clues?

Honestly I have no idea… I guess the best would be to take advantage of dependency injection of jersey to use the desired configuration based on the context? I'm not sure how that works… maybe if you put a Feature (like the SecurityFeature) in your test classpath, it will be activated only in the test context and in it you could inject some specific services… I'm not really sure :)
 

> I share your feeling about using it in a way that was not intended,
> but that only concerns the authentication (in the sense of going from
> a login/pass to a jwt token for you, and a login/pass to an http
> session for me).

Indeed. I think the term "callback" is what's confusing, it's really
from a very specific point of view, I think a more general "endpoint"
would be less confusing and more generic...

Could be, or at least for jax-rs-pac4j… also as you highlighted above, it could be interesting to be able to set the client to use on the callback endpoint!

> Yes it does send a 401 response and yes you can control the payload,
> but it will feel like a hack too, as before. There is room for
> improvement I guess on that side too :)

Ok thank you very much! What is the way to control the payload in case
of an error?

Not 100% sure, it should be doable. There has been some discussion on the matter in the past here: https://groups.google.com/d/topic/pac4j-users/EaCtHEgiWm4/discussion
I think there is a working solution there!

Élie Roux

unread,
Jun 16, 2017, 8:03:32 AM6/16/17
to victo...@gmail.com, Pac4j users mailing list
> In your case, there is no reason for plugin a jwt client to the
> callback url! The jwt client will serve to protect urls, not do the
> authentication on the callback url. But if you wanted to do it, then
> you would need to pass the name of the client as a parameter to the
> callback url call!

I'm not sure I'm following... but what is the limitting factor here,
pac4j or jax-rs-pac4j? I think it would make sense to open a feature
request on github, to allow broader discussion... wdyt?

> Again, that's maybe why it could be simpler NOT to use the pac4j
> callback mechanism to do the primary authentication (from login/pass
> to jwt) but only the pac4j client to to the direct (i.e., for every
> call) authentication based on the jwt token. That's a choice you
> have to make :)

Ok... I'm not entirely sure what the benefit of it would be... For
instance, with a code similar to your example, will I be able to force
JWT authentication on other URLs (like /user/get/me for instance)? If
not then I have to do as you just described...

All this is, as you can see, really not obvious for people like me who
have what I think could be qualified as a very standard and simple use
case in mind... I think there's quite a lot of room for doc improvements
here!

> Honestly I have no idea… I guess the best would be to take advantage
> of dependency injection of jersey to use the desired configuration
> based on the context? I'm not sure how that works… maybe if you put
> a Feature (like the SecurityFeature) in your test classpath, it will
> be activated only in the test context and in it you could inject
> some specific services… I'm not really sure :)

That's what I'm suspecting, I'll make a few trials in this direction.

> Could be, or at least for jax-rs-pac4j… also as you highlighted
> above, it could be interesting to be able to set the client to use
> on the callback endpoint!

This I think a github ticket should be opened, would that be ok?

> Not 100% sure, it should be doable. There has been some discussion on
> the matter in the past here:
> https://groups.google.com/d/topic/pac4j-users/EaCtHEgiWm4/discussion
> I think there is a working solution there!

Doesn't look very straightforward but I'll try it!

Thank you!
--
Elie

Élie Roux

unread,
Jun 16, 2017, 1:00:16 PM6/16/17
to victo...@gmail.com, Pac4j users mailing list
Hello,

I have managed to advance a little bit and now have something that, I
think, should work for tests (not sure yet how it will run in production):

https://github.com/BuddhistDigitalResourceCenter/auth-api

but I'm getting an error I have not much clue how to fix:

TechnicalException: request cannot be null

complete log on

https://pastebin.com/mHbRazUs

What is the cause of this exception?

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 16, 2017, 1:17:05 PM6/16/17
to Pac4j users mailing list, victo...@gmail.com
I will answer tomorrow to the rest of the discussion, but for this problem, I think it may be related to the fact that the default JerseyTest is not a servlet based container (but the in-memory one).
Either you make it use a servlet based container (see for example my https://github.com/pac4j/jax-rs-pac4j/blob/master/src/test/java/org/pac4j/jax/rs/rules/JerseyGrizzlyServletRule.java as well as JerseyRule (that setups the JerseyTest), either you configure pac4j for not rely on a servlet container by replacing ServletJaxRsContextFactoryProvider with JaxRsContextFactoryProvider (but you won't get session support, I don't think it is so important for your use case).
See https://github.com/pac4j/jax-rs-pac4j/tree/master/src/test/java/org/pac4j/jax/rs/rules for the various working combinations :)

Élie Roux

unread,
Jun 16, 2017, 1:27:42 PM6/16/17
to victo...@gmail.com, Pac4j users mailing list
Le 16/06/2017 à 19:17, victo...@gmail.com a écrit :
> I will answer tomorrow to the rest of the discussion, but for this
> problem, I think it may be related to the fact that the default
> JerseyTest is not a servlet based container (but the in-memory one).
> Either you make it use a servlet based container (see for example my
> https://github.com/pac4j/jax-rs-pac4j/blob/master/src/test/java/org/pac4j/jax/rs/rules/JerseyGrizzlyServletRule.java
> as well as JerseyRule (that setups the JerseyTest), either you configure
> pac4j for not rely on a servlet container by replacing
> ServletJaxRsContextFactoryProvider with JaxRsContextFactoryProvider (but
> you won't get session support, I don't think it is so important for your
> use case).

Indeed. I've tried replacing ServletJaxRsContextFactoryProvider by
JaxRsContextFactoryProvider (I've pushed on Github), and I now have

TechnicalException: callbackUrl cannot be blank

Full log:

https://pastebin.com/piXz9nVk
Interesting... I have to admit I have no clue what these rules are...
are these standard Jersey things?

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 16, 2017, 1:31:59 PM6/16/17
to Pac4j users mailing list, victo...@gmail.com


Le vendredi 16 juin 2017 19:27:42 UTC+2, Élie Roux a écrit :
Le 16/06/2017 à 19:17, victo...@gmail.com a écrit :
> I will answer tomorrow to the rest of the discussion, but for this
> problem, I think it may be related to the fact that the default
> JerseyTest is not a servlet based container (but the in-memory one).
> Either you make it use a servlet based container (see for example my
> https://github.com/pac4j/jax-rs-pac4j/blob/master/src/test/java/org/pac4j/jax/rs/rules/JerseyGrizzlyServletRule.java
> as well as JerseyRule (that setups the JerseyTest), either you configure
> pac4j for not rely on a servlet container by replacing
> ServletJaxRsContextFactoryProvider with JaxRsContextFactoryProvider (but
> you won't get session support, I don't think it is so important for your
> use case).

Indeed. I've tried replacing ServletJaxRsContextFactoryProvider by
JaxRsContextFactoryProvider (I've pushed on Github), and I now have

TechnicalException: callbackUrl cannot be blank

Good :) you are now missing some more configuration, I think you simply need to fill the callback url on the config (with the url of your authentication endpoint, I guess /api/login?) but actually it won't be used: it is used for redirecting to a login page in case access to a protected resource as I was explaining before. Since you don't rely on that aspect of pac4j, you could even put some garbage for the callback url.
 
 

> See
> https://github.com/pac4j/jax-rs-pac4j/tree/master/src/test/java/org/pac4j/jax/rs/rules
> for the various working combinations :)

Interesting... I have to admit I have no clue what these rules are...
are these standard Jersey things?

Lol no, the rules I have written, but they each configure JerseyTest with a different combination of container (in memory, grizzly and grizzly servlet), pac4j configuration for each and JerseyTest initialisation.
The containers are from Jersey yes :)
 

Thank you!
--
Elie

Élie Roux

unread,
Jun 16, 2017, 1:41:42 PM6/16/17
to victo...@gmail.com, Pac4j users mailing list
Thanks for your quick answers!

> Good :) you are now missing some more configuration, I think you
> simply need to fill the callback url on the config (with the url of
> your authentication endpoint, I guess /api/login?) but actually it
> won't be used: it is used for redirecting to a login page in case
> access to a protected resource as I was explaining before. Since you
> don't rely on that aspect of pac4j, you could even put some garbage
> for the callback url.

I think I've done that in

https://github.com/BuddhistDigitalResourceCenter/auth-api/commit/e320047cd29563934d74981286f3f7b26cef59e6

now I'm getting a java.lang.NullPointerException, full log on

https://pastebin.com/AXA1Rmz5

> Lol no, the rules I have written, but they each configure JerseyTest
> with a different combination of container (in memory, grizzly and
> grizzly servlet), pac4j configuration for each and JerseyTest
> initialisation. The containers are from Jersey yes :)

Not sure I understand, I'll take a look later, looks interesting!

Thank you,
--
Elie

victo...@gmail.com

unread,
Jun 17, 2017, 6:05:03 AM6/17/17
to Pac4j users mailing list, victo...@gmail.com


Le vendredi 16 juin 2017 14:03:32 UTC+2, Élie Roux a écrit :
> In your case, there is no reason for plugin a jwt client to the
> callback url! The jwt client will serve to protect urls, not do the
> authentication on the callback url. But if you wanted to do it, then
> you would need to pass the name of the client as a parameter to the
> callback url call!

I'm not sure I'm following... but what is the limitting factor here,
pac4j or jax-rs-pac4j? I think it would make sense to open a feature
request on github, to allow broader discussion... wdyt?

It is the current design of pac4j I think, the callback is meant to be a global thing in pac4j: there is one callback url with one callback endpoint and all (indirect) clients end their authentication process via this url.
I created an issue: https://github.com/pac4j/pac4j/issues/938
Until then, I think we can hack something into jax-rs-pac4j for this kind of usage, maybe introducing another annotation that would be more "endpoint-oriented" as you said and that would setup things as desired :)

> Again, that's maybe why it could be simpler NOT to use the pac4j
> callback mechanism to do the primary authentication (from login/pass
> to jwt) but only the pac4j client to to the direct (i.e., for every
> call) authentication based on the jwt token. That's a choice you
> have to make :)

Ok... I'm not entirely sure what the benefit of it would be... For
instance, with a code similar to your example, will I be able to force
JWT authentication on other URLs (like /user/get/me for instance)? If
not then I have to do as you just described...

This wouldn't change anything for the use of pac4j for the rest of the application.
I'm thinking about:
 - protecting urls with pac4j using a direct http client configured with jwt
 - generating the jwt upon login via a normal jax-rs endpoint, by exploiting the services provided by pac4j for the jwt generation.
 

All this is, as you can see, really not obvious for people like me who
have what I think could be qualified as a very standard and simple use
case in mind... I think there's quite a lot of room for doc improvements
here!

Yes it is true, I have not so much time for writing documentation (and I'm not very good at it I think... :P also I know how everything work, so I don't know where something is missing) so maybe if you have some ideas of some sentences to add to the README to make things smoother, don't hesitate to make a PR. No need for something complex, but more like some clarifications in key points where you know you were blocked!
 

> Honestly I have no idea… I guess the best would be to take advantage
> of dependency injection of jersey to use the desired configuration
> based on the context? I'm not sure how that works… maybe if you put
> a Feature (like the SecurityFeature) in your test classpath, it will
> be activated only in the test context and in it you could inject
> some specific services… I'm not really sure :)

That's what I'm suspecting, I'll make a few trials in this direction.

Yep, it's not obvious, I think you should take advantage of the dependency injection framework of jersey (even though it's not pure jax-rs, so it will tie you to jersey)
 

> Could be, or at least for jax-rs-pac4j… also as you highlighted
> above, it could be interesting to be able to set the client to use
> on the callback endpoint!

This I think a github ticket should be opened, would that be ok?

See at the beginning of this message :)

> Not 100% sure, it should be doable. There has been some discussion on
> the matter in the past here:
> https://groups.google.com/d/topic/pac4j-users/EaCtHEgiWm4/discussion
>  I think there is a working solution there!

Doesn't look very straightforward but I'll try it!

I saw you made a HttpActionAdapter, it's good, you should extend JaxRsHttpActionAdapter directly and call super.adapt for the nominal case, it would be easier to maintain in the long term I think :)
 

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 17, 2017, 6:20:26 AM6/17/17
to Pac4j users mailing list, victo...@gmail.com


Le vendredi 16 juin 2017 19:41:42 UTC+2, Élie Roux a écrit :
Thanks for your quick answers!

> Good :) you are now missing some more configuration, I think you
> simply need to fill the callback url on the config (with the url of
> your authentication endpoint, I guess /api/login?) but actually it
> won't be used: it is used for redirecting to a login page in case
> access to a protected resource as I was explaining before. Since you
> don't rely on that aspect of pac4j, you could even put some garbage
> for the callback url.

I think I've done that in

https://github.com/BuddhistDigitalResourceCenter/auth-api/commit/e320047cd29563934d74981286f3f7b26cef59e6

now I'm getting a java.lang.NullPointerException, full log on

https://pastebin.com/AXA1Rmz5

Ah yes, it is because since you are using a session-less container, you should disable session reading from the pac4jprofile annotation:
@Pac4JProfile(readFromSession=false)

In any case, the alternative, which is better, is to simply setup JerseyTest to use a session-enabled container.
If you plan to run your application on a servlet container, the best is to use the following:
Replace the configure override in MyAppTest with:
    @Override
   
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
       
return new GrizzlyWebTestContainerFactory();
   
}
   
   
@Override
   
protected DeploymentContext configureDeployment() {
       
ResourceConfig rc = new ResourceConfig(MyResource.class);
        rc
.register(MyAuthResource.class);
        rc
.register(TestSecurityFeature.class);
       
return ServletDeploymentContext.forServlet(new ServletContainer(rc)).build();
   
}

Add the following test dependency to your pom.xml:
        <dependency>
         
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
         
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
         
<scope>test</scope>
       
</dependency>

Use ServletJaxRsContextFactoryProvider in your security feature (instead of JaxRsContextFactoryProvider).

Do not use readFrmSession=false, set something like "notUsed" for the callback url (so that it is explicit that it is not meant to be used!).
By the way, it would be cleaner to set the fake callback url directly on the client, so that maybe later you are open to use a real one for other clients if it makes sense:
loginClient.setCallbackUrl("notUsed");

Élie Roux

unread,
Jun 17, 2017, 7:14:42 AM6/17/17
to victo...@gmail.com, Pac4j users mailing list
Hello,
Thanks a lot!

> Until then, I think we can hack something into jax-rs-pac4j for this
> kind of usage, maybe introducing another annotation that would be
> more "endpoint-oriented" as you said and that would setup things as
> desired :)

That would probably be a good idea!

> This wouldn't change anything for the use of pac4j for the rest of
> the application. I'm thinking about: - protecting urls with pac4j
> using a direct http client configured with jwt - generating the jwt
> upon login via a normal jax-rs endpoint, by exploiting the services
> provided by pac4j for the jwt generation.

I think it's a good solution, I'll probably go this way, but I'd like to
make the simple first mechanism work, for the sake of having something
simple working and then moving to something else (I'm quite stubborn in
trying to finish something and to it correctly before moving on)

> Yes it is true, I have not so much time for writing documentation
> (and I'm not very good at it I think... :P also I know how
> everything work, so I don't know where something is missing) so maybe
> if you have some ideas of some sentences to add to the README to make
> things smoother, don't hesitate to make a PR. No need for something
> complex, but more like some clarifications in key points where you
> know you were blocked!

I may do so indeed! One thing though: I think all the errors I
encountered are certainly relatively common and have useless error
messages (when they have a message at all). It would certainly be
relevant to catch these cases in the code and output a configuration
error or something similar... I can open a ticket for that if you think
it is relevant.

> Yep, it's not obvious, I think you should take advantage of the
> dependency injection framework of jersey (even though it's not pure
> jax-rs, so it will tie you to jersey)

I'm fine with Jersey, no problem

> I saw you made a HttpActionAdapter, it's good, you should extend
> JaxRsHttpActionAdapter directly and call super.adapt for the nominal
> case, it would be easier to maintain in the long term I think :)

Done, looks good!

> Ah yes, it is because since you are using a session-less container,
> you should disable session reading from the pac4jprofile annotation:
> @Pac4JProfile(readFromSession=false)

I think I want that (as I'd like to avoid using sessions at all if
possible), but I'm not really sure where to add the annotation... I've
tried to add it in MyAuthResource:

https://github.com/BuddhistDigitalResourceCenter/auth-api/commit/970348e374330775c15ff7502e2a3ffd32d9ee06

but I'm not sure where I'm supposed to do that... is it the correct place?

Also, now I'm getting an error log that looks slightly better but still
wrong (the test is not supposed to be failing):

https://pastebin.com/JWJrybKc

Thank you!
--
Elie

victo...@gmail.com

unread,
Jun 17, 2017, 7:21:24 AM6/17/17
to Pac4j users mailing list, victo...@gmail.com


Le samedi 17 juin 2017 13:14:42 UTC+2, Élie Roux a écrit :

> Until then, I think we can hack something into jax-rs-pac4j for this
>  kind of usage, maybe introducing another annotation that would be
> more "endpoint-oriented" as you said and that would setup things as
> desired :)

That would probably be a good idea!

I will think more about it in the next few days... I will see how the rest of the pac4j team react to the issues I opened.
 

> Yes it is true, I have not so much time for writing documentation
> (and I'm not very good at it I think... :P also I know how
> everything work, so I don't know where something is missing) so maybe
> if you have some ideas of some sentences to add to the README to make
> things smoother, don't hesitate to make a PR. No need for something
> complex, but more like some clarifications in key points where you
> know you were blocked!

I may do so indeed! One thing though: I think all the errors I
encountered are certainly relatively common and have useless error
messages (when they have a message at all). It would certainly be
relevant to catch these cases in the code and output a configuration
error or something similar... I can open a ticket for that if you think
it is relevant.

Yep, but I think it should be opened in pac4j directly.
That could be a good idea to print, with the error, some indication of where this should/could be configured (for example the callback url is configurable either on the global pac4j Config object or in an IndirectClient object).
Could you open an issue, with the list of the cryptic errors you encountered (luckily they are recorded in this discussion :) and some example of the expect error message (for example use the callback url one as an example).


> Ah yes, it is because since you are using a session-less container,
> you should disable session reading from the pac4jprofile annotation:
>  @Pac4JProfile(readFromSession=false)

I think I want that (as I'd like to avoid using sessions at all if
possible), but I'm not really sure where to add the annotation... I've
tried to add it in MyAuthResource:

https://github.com/BuddhistDigitalResourceCenter/auth-api/commit/970348e374330775c15ff7502e2a3ffd32d9ee06

It's just there next to the method parameter 2 lines below :P

Jérôme LELEU

unread,
Jun 17, 2017, 12:08:48 PM6/17/17
to Victor Noel, Pac4j users mailing list
Hi,

I see a long discussion here I read too quickly.

Yes, at first, pac4j was meant to handle UI login, but REST API are fully and seamlessly integrated since version 1.9.

I think many information are already in the documentation: http://www.pac4j.org/docs/index.html

Especially the main concepts: http://www.pac4j.org/docs/main-concepts-and-components.html and a better understanding of what a client is: http://www.pac4j.org/docs/clients.html

A direct client is for web services authentication (REST API) while an indirect client is for UI login.

That said, there is certainly room for improvement in the documentation and I'll welcome any contribution (read that first: http://www.pac4j.org/docs/contribute.html).

I will take a look at the opened issues.

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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jérôme LELEU

unread,
Jun 17, 2017, 12:09:38 PM6/17/17
to Victor Noel, Pac4j users mailing list
I forgot to say that I'm preparing a "Getting started" guide that I hope to be able to publish soon...

victo...@gmail.com

unread,
Jun 17, 2017, 12:15:03 PM6/17/17
to Pac4j users mailing list, victo...@gmail.com
Jerome, you are totally right, as long as we talk about protecting URL.

The main need here is about setting up a new session, it being an http session (like the situation I had to face with my application) or a jwt session (i.e., generating a jwt token from a user/pass compared to one stored in a db for example, that can be later on used by a direct client that protects URL).

The http session use case is almost seamless with the current state of pac4j (even though the redirection aspect of pac4j has to be disabled if you want to have a pure REST/AJAX API for authentication) because you can use the same (indirect) client to both authenticate against the db AND authenticate rest calls once the session is setup.
But the JWT use case is not so well integrated, and actually, I think it is often easier just to handle the jwt generation part of the question outside of pac4j, and use pac4j only for the direct authentication on resource access.

So there is still some things that are not very well covered, but maybe it is not meant to be the case actually :)
To unsubscribe from this group and stop receiving emails from it, send an email to pac4j-users...@googlegroups.com.

Jérôme LELEU

unread,
Jun 19, 2017, 8:51:16 AM6/19/17
to Victor Noel, Pac4j users mailing list
Hi,

I'm not sure to follow you completely on the JWT session. It's true we don't have a JwtSessionStore, but where would you save the generated JWT? In a cookie?

If you want to generate a JWT, indeed, you need to manually call the JwtGenerator and return it to the caller (in JSON, XML...): I guess you expected something automatic here?

Thanks.
Best regards,
Jérôme






To unsubscribe from this group and stop receiving emails from it, send an email to pac4j-users+unsubscribe@googlegroups.com.

victo...@gmail.com

unread,
Jun 19, 2017, 9:11:29 AM6/19/17
to Pac4j users mailing list, victo...@gmail.com


Le lundi 19 juin 2017 14:51:16 UTC+2, Jérôme LELEU a écrit :
Hi,

I'm not sure to follow you completely on the JWT session. It's true we don't have a JwtSessionStore, but where would you save the generated JWT? In a cookie?

No no, not at all, you are right, it's not where is the problem.
 

If you want to generate a JWT, indeed, you need to manually call the JwtGenerator and return it to the caller (in JSON, XML...): I guess you expected something automatic here?

Yes, I think that's the idea: purely in terms of use case, the generation of the jwt on the callback and then the use of the same jwt to authenticate requests is not conceptually supported by pac4j.
I understand why, it is just that it's a use case that is not supported.
As you say, the use of jwt in pac4j wasn't meant to cover the generation of the jwt automatically from an initial authentication request.
And I think that I tried to use pac4j to do that for Élie use case, even though it's not meant to be :)
 

Jérôme LELEU

unread,
Jun 19, 2017, 9:18:50 AM6/19/17
to Victor Noel, Pac4j users mailing list
Hi,

In fact, there is one main reason for not supporting this use case: indirect clients are meant for users interacting with UI, thus the callback and the data saved into the session. A user should not handle a returned JWT, this is generally another use case where a server communicates with another one and for that, you can use a direct client to receive user's credentials. Even if you still need to generate the JWT on your own.

Thanks.
Best regards,
Jérôme


To unsubscribe from this group and stop receiving emails from it, send an email to pac4j-users+unsubscribe@googlegroups.com.

victo...@gmail.com

unread,
Jun 22, 2017, 9:21:06 AM6/22/17
to Pac4j users mailing list, victo...@gmail.com
So, concerning the login endpoint, after discussing with Jérome (there: https://github.com/pac4j/pac4j/issues/939), I think for your application you could have:
  • one direct client configured with your custom extractor as discussed to retrieve the body and authenticate it against the couchdb authenticator to protect the "/login" endpoint, which would then simply return the generated JWT (using JwtGenerator). You should thus use the @Pac4JSecurity with the name of the client passed as a parameter to protect the endpoint in your resource class.
  • one direct client from pac4j-http for header configured with a jwt authenticator to protect the rest of the URLs.
What do you think Élie ? It would greatly simplify the configuration (no more callback url to configure, no more hack with the @Pac4JCallback annotation, etc).

Élie Roux

unread,
Jun 22, 2017, 9:31:01 AM6/22/17
to victo...@gmail.com, Pac4j users mailing list
Le 22/06/2017 à 15:21, victo...@gmail.com a écrit :
> So, concerning the login endpoint, after discussing with Jérome
> (there: https://github.com/pac4j/pac4j/issues/939),

Thanks for coming back on that!

> I think for your application you could have:
>
> * one direct client configured with your custom extractor as
> discussed to retrieve the body and authenticate it against the
> couchdb authenticator to protect the "/login" endpoint, which would
> then simply return the generated JWT (using |JwtGenerator|).

Sounds sensible indeed

> You should thus use the |@Pac4JSecurity| with the name of the client
> passed as a parameter to protect the endpoint in your resource
> class.

Ok, I see!

> * one direct client from pac4j-http for header configured with a jwt
> authenticator to protect the rest of the URLs.

Ok I see the general direction, sounds very good!

Thank you very much,
--
Elie
Reply all
Reply to author
Forward
0 new messages