Custom OAuth Bearer token handling

568 views
Skip to first unread message

Cyril Delmas

unread,
Oct 23, 2015, 4:24:41 AM10/23/15
to vert.x
Hello all,
I have a tiny vert.x toy app (I give a try, it seems awesome!), and now, I want to add a protection. This "protection" is simply a Facebook token passed in the request's Authorization header. I was inspired by this implementation that I adapted to the last version of the AuthProvider API: https://gist.github.com/beckje01/c245f1ebf9aab022b81d.

When I execute the code with the protection, I get the following error:

oct. 23, 2015 10:03:39 AM io.vertx.ext.web.impl.RoutingContextImplBase
GRAVE: Unexpected exception in route
java.lang.IllegalStateException: Request has already been read
at io.vertx.core.http.impl.HttpServerRequestImpl.checkEnded(HttpServerRequestImpl.java:409)
at io.vertx.core.http.impl.HttpServerRequestImpl.setExpectMultipart(HttpServerRequestImpl.java:310)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.<init>(BodyHandlerImpl.java:86)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.<init>(BodyHandlerImpl.java:75)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl.handle(BodyHandlerImpl.java:49)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl.handle(BodyHandlerImpl.java:36)
at io.vertx.ext.web.impl.RouteImpl.handleContext(RouteImpl.java:218)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:67)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:96)
at io.github.cdelmas.spike.vertx.infrastructure.auth.BearerAuthHandler.lambda$handle$2(BearerAuthHandler.java:53)
at io.github.cdelmas.spike.vertx.infrastructure.auth.BearerAuthHandler$$Lambda$17/1817772640.handle(Unknown Source)
at io.github.cdelmas.spike.vertx.infrastructure.auth.FacebookOauthTokenVerifier.lambda$null$0(FacebookOauthTokenVerifier.java:47)
at io.github.cdelmas.spike.vertx.infrastructure.auth.FacebookOauthTokenVerifier$$Lambda$30/567805533.handle(Unknown Source)
at io.vertx.core.http.impl.HttpClientResponseImpl$BodyHandler.notifyHandler(HttpClientResponseImpl.java:261)
at io.vertx.core.http.impl.HttpClientResponseImpl$1.handle(HttpClientResponseImpl.java:168)
at io.vertx.core.VoidHandler.handle(VoidHandler.java:27)
at io.vertx.core.VoidHandler.handle(VoidHandler.java:24)
at io.vertx.core.http.impl.HttpClientResponseImpl.handleEnd(HttpClientResponseImpl.java:226)
at io.vertx.core.http.impl.ClientConnection.handleResponseEnd(ClientConnection.java:283)
at io.vertx.core.http.impl.HttpClientImpl$ClientHandler.doMessageReceived(HttpClientImpl.java:781)
at io.vertx.core.http.impl.HttpClientImpl$ClientHandler.doMessageReceived(HttpClientImpl.java:749)
at io.vertx.core.http.impl.VertxHttpHandler.lambda$channelRead$17(VertxHttpHandler.java:80)
at io.vertx.core.http.impl.VertxHttpHandler$$Lambda$25/2018741543.run(Unknown Source)
at io.vertx.core.impl.ContextImpl.lambda$wrapTask$15(ContextImpl.java:312)
at io.vertx.core.impl.ContextImpl$$Lambda$16/1125936222.run(Unknown Source)
at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:217)
at io.vertx.core.http.impl.VertxHttpHandler.channelRead(VertxHttpHandler.java:80)
at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:124)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:308)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:294)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:244)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:147)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:308)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:294)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1069)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:939)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:327)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:230)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:308)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:294)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:131)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:111)
at java.lang.Thread.run(Thread.java:745)


I set the auth handler like this:
Router router = Router.router(vertx);
AuthHandler auth = new BearerAuthHandler(new FacebookOauthTokenVerifier(httpClient));
router.route("/*").handler(auth);

When I remove this part, the app runs with no problem, so I guess something is wrong in one of the two classes (BearerAuthHandler or FacebookOauthTokenVerifier), but I don't understand what. Any suggestion?

Thanks in advance, cheers.
Cyril.
BearerAuthHandler.java
FacebookOauthTokenVerifier.java
MyUser.java

Cyril Delmas

unread,
Nov 1, 2015, 3:35:19 PM11/1/15
to vert.x
After trying hard to figure out what is the problem, I have found that in the  authenticate method of FacebookOauthTokenVerifier, I make a http request, with a handler calling the resultHandler which is set to call routingContext.next() (the handler is set in BearerAuthHandler). Is it possible that the http request's handler, being asynchronous, was called after the request is ended? In fact, when I replace the http request with this code:

resultHandler.handle(Future.succeededFuture(new MyUser(1234567890L, "Bob Klazz", token))); 

I don't get the exception. I guess it's the problem, but how can I work around this? 

Thank you for any suggestion.

Paulo Lopes

unread,
Nov 2, 2015, 6:45:25 AM11/2/15
to vert.x
Hi Cyril,

For 3.2 we are working on getting OAuth2 support into the framework, at the moment there are 2 pull requests (one on auth and another in web)

https://github.com/vert-x3/vertx-web/pull/244
https://github.com/vert-x3/vertx-auth/pull/40

It includes an example on authenticating using github, i guess that for facebook it would be the same you would only need to change the configuration. If you're interested you could help review and check if it works for facebook.

Cheers,
Paulo

Jez P

unread,
Nov 3, 2015, 6:36:58 AM11/3/15
to vert.x
Hi Paulo,

I think I'm getting near the end of the vertx-pac4j stuff (working on the demo app now, which has shown up a bug in my implementation). Once that's done I'll jump back to helping out with the non-blocking-oauth implementation if there's anything useful I can do. 

Jérôme at Pac4j is interested in making Pac4j reactive, which could mean (if we can do it cleanly in a reasonable time) that we could consider using a non-blocking pac4j library for all auth going forward, however he and I haven't really looked at the detail of this and its implications, but there's a potential benefit in that approach in terms of letting the pac4j team manage the detail of auth in future and vertx providing a wrapper for a non-blocking pac4j lib. That said, we haven't explored what's required (including what third party libs would have to be replaced with a potentially non-blocking implementation) so we're still at a talking stage rather than a doing stage, and I need to get the current vertx implementation done before we even explore in any detail. Just thought I'd let you know of the pac4j team's thinking. Right now (pac4j 1.8) the underlying implementation does include blocking i/o and therefore is wrapped in executeBlocking, which is not as desirable as a fully non-blocking implementation.

Cheers,

Jez

Jérôme LELEU

unread,
Nov 4, 2015, 4:25:26 AM11/4/15
to vert.x
Hi,

Jez is right, but I'll go further on this.

pac4j started as a refactoring of the Facebook authentication delegation I used in the CAS server and in the Shiro library and pac4j has grown up with this idea: pac4j is a security engine made to be reused by libraries because all libraries almost have the same needs when it comes to security and experts should focus / gather their efforts. pac4j exists to prevent people from reinventing the wheel in terms of security.

Vertx 3.2 can certainly have a good support for OAuth 2.0, but what about the CAS support? SAML support? Will you recreate them also?

We had several discussions with Tim before Vertx 3 and even people I don't know were pushing for the use of pac4j in Vertx, but finally, I see a vertx-auth-shiro module in vertx-auth project and guess how OAuth, CAS, SAML... supports work in Shiro: we use buji-pac4j.

pac4j is blocking and yes, this is a problem for Vertx, I fully understand that. Notice it's also a problem for Ratpack and Play, but currently, they also deal with it using some "executeBlocking" trick.

So here is my proposal to the Vertx community: stop working on authentication/authorization mechanisms already available in pac4j, help us make pac4j non-blocking and use pac4j as the core security engine in Vertx.

I'm willing to make the necessary efforts to achieve this reconciliation. We should work together in the same direction.

I'll be happy to welcome a Vertx committer in the pac4j development team, like Paulo for example (Luke Daley from Ratpack is a pac4j committer). I don't want to embarrass Jez, but for me, another valid option is to propose him as the Vertx maintainer of this new Vertx / pac4j security module.

Thanks.
Best regards,
Jérôme

Cyril Delmas

unread,
Nov 4, 2015, 5:10:30 AM11/4/15
to vert.x
Thank you all for the responses.

It all seems very interesting, but my use case is very simple: my REST API receives a http request with a Authorization header containing a token (e.g. Bearer 32eca13....), and I need to validate the token and retrieve the user profile (at least its name and ID) from the provider (Facebook in this case). I don't need the complete authentication flow provided by pac4j / vertx-auth as far as I understand, as I assume that the authentication is done before the call to the API (e.g. in a javascript client). In fact, the TokenVerifier just do a http request in an asynchronous handler, which I think is simply a pure vertx problem (for which I have no solution yet).

Anyway, if you can help me with either solution, I am always looking for a working solution.

Cheers,
Cyril.

Jérôme LELEU

unread,
Nov 4, 2015, 5:24:04 AM11/4/15
to ve...@googlegroups.com
Hi,

I'll let Jez provide you a more comprehensive reply, but vertx-pac4j v2 (like all the libraries based on pac4j v1.8) supports most authentication mechanisms, called clients:

- indirect / stateful clients are for UI when the user authenticates once at an external provider (like Facebook, a CAS server...) or via a local form (or basic auth popup)
- direct / stateless clients are for web services when credentials (like basic auth, tokens...) are passed for each HTTP request.

So, if you use vertx-pac4j, you need a HeaderClient to get your token from the request and the right Authenticator to validate it (built on: https://github.com/pac4j/pac4j/blob/master/pac4j-http/src/main/java/org/pac4j/http/credentials/authenticator/TokenAuthenticator.java).

Thanks.
Best regards,
Jérôme


--
You received this message because you are subscribed to a topic in the Google Groups "vert.x" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/vertx/aoOcy2N0MSY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to vertx+un...@googlegroups.com.
Visit this group at http://groups.google.com/group/vertx.
To view this discussion on the web, visit https://groups.google.com/d/msgid/vertx/4c3b4f5d-c54a-406b-891b-53023e96e604%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Paulo Lopes

unread,
Nov 4, 2015, 8:27:16 AM11/4/15
to vert.x
Hi Cyril,

The current (in review OAuth2 auth provider works and is documented in regards to GitHub and Google Auth) from what i read you only need the token for your code so i guess something in these lines would work for you:

OAuth2Auth authProvider = OAuth2Auth.create(vertx, OAuth2FlowType.CLIENT, new JsonObject()
.put("clientID", "your-client-id")
.put("clientSecret", "your-client-secret")
.put("site", "https://graph.facebook.com")
.put("tokenPath", "/oauth/access_token"));


authProvider.getToken(new JsonObject(), res -> {
if (res.succeeded()) {
User user = res.result();
// if you need to know more about the user or do any expiration/revoke stuff...
AccessToken token = res.result();

token.revoke(...)
token.expired()
token.refresh(...)
}
});


I haven't tested this code i but it should be in these lines i think...

Paulo Lopes

unread,
Nov 4, 2015, 10:01:32 AM11/4/15
to vert.x


On Wednesday, November 4, 2015 at 10:25:26 AM UTC+1, Jérôme LELEU wrote:
Hi,

Jez is right, but I'll go further on this.

pac4j started as a refactoring of the Facebook authentication delegation I used in the CAS server and in the Shiro library and pac4j has grown up with this idea: pac4j is a security engine made to be reused by libraries because all libraries almost have the same needs when it comes to security and experts should focus / gather their efforts. pac4j exists to prevent people from reinventing the wheel in terms of security.

Vertx 3.2 can certainly have a good support for OAuth 2.0, but what about the CAS support? SAML support? Will you recreate them also?

We had several discussions with Tim before Vertx 3 and even people I don't know were pushing for the use of pac4j in Vertx, but finally, I see a vertx-auth-shiro module in vertx-auth project and guess how OAuth, CAS, SAML... supports work in Shiro: we use buji-pac4j.

pac4j is blocking and yes, this is a problem for Vertx, I fully understand that. Notice it's also a problem for Ratpack and Play, but currently, they also deal with it using some "executeBlocking" trick.

So here is my proposal to the Vertx community: stop working on authentication/authorization mechanisms already available in pac4j, help us make pac4j non-blocking and use pac4j as the core security engine in Vertx.

This is a very bold statement to make, vert.x is an open source project and if someone wants to fork or create a new piece of code they are free to do so and we as an open source should encourage it. Diversity is good and the fuel for open source. Having said this, I'd like to see pac4j evolve into the async mode as well any other library out there and leave to our users to decide which one they prefer to use.

Jez P

unread,
Nov 4, 2015, 12:05:43 PM11/4/15
to vert.x
I kind of agree with both sides on this discussion, diversity is good for open source, but on the reverse side, if pac4j can expose a reactive API which lends itself to a non-blocking vertx adapter (as opposed to the current vertx adapter which uses executeBlocking) it might save vert.x maintainers from implementing every security mechanism already done in pac4j and those which will be built in future. Plus if pac4j gains traction across technologies, there's a case for someone migrating to vert.x to move their security knowledge more easily if they already have pac4j and if there's a pac4j adapter to use. 

Obviously I'm a bit biased because I'm a fan of both projects ;) But I'll be looking to play my part in the development of pac4j 1.9 as a reactive API (into which different internal http clients can be plugged, enabling use of a vert.x http client for doing auth provider contact in the vert.x adapter, rather than the blocking client in use at present) with a view to making the vertx-pac4j implementation non-blocking end to end. Maybe we'll end up with effectively duplication between vert.x authentication/authorization libs and vertx-pac4j ones as a result, and as you say users can take their pick.

I also appreciate that the main reason for the work you've been doing on the oauth stuff is because I switched to the vertx-pac4j implementation and it's taken quite a bit longer than I expected (and the only value for the non-blocking oAuth stuff I left behind was as a "my first oAuth authenticator" project as it only passed a single happy path test) - had I finished the underlying blocking vertx-pac4j then anyone who wants oAuth could immediately use vertx-pac4j but that is still not available yet (one bug outstanding plus a few endpoints on the demo project), so I'm sorry for that.  

Tim Fox

unread,
Nov 4, 2015, 12:20:41 PM11/4/15
to ve...@googlegroups.com
+1

My 2c here:

vertx-auth needs a simple OAuth based auth mechanism, it's currently an obvious hole in our offering for people creating modern webapps.

We need to solve the 90% use case - i.e. simple OAuth using one of the main providers - Facebook, Google, GitHub, Linkedin etc - that's what most of our users need. So that's why Paulo has been working on this simple implementation.

There is no intention to turn it into a fully featured auth library like pac4j, we just have a pressing need to get a simple non blocking OAuth impl that lets 90% of our users do what they want to do, and we need it quickly (i.e. in the next few weeks before Vert.x 3.2)

I think a vertx-pac4j would be a great addition to the Vert.x ecosystem, especially for those users who want more advanced auth functionality that's not provided by the vertx-auth simple implementation, and it would be even better if it was non blocking :)
-- You received this message because you are subscribed to the Google Groups "vert.x" group. To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com. Visit this group at http://groups.google.com/group/vertx. To view this discussion on the web, visit https://groups.google.com/d/msgid/vertx/523e0644-ff00-4f77-b287-ada756a0f2ec%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.

Cyril Delmas

unread,
Nov 6, 2015, 4:09:52 PM11/6/15
to vert.x
Hello all,
I finally managed to make the thing work: in BearerAuthHandler's handle method, the request was already read when calling routingContext.next(), so I had to pause it, then resume it before the call to routingContext.next() :

//BearerAuthHandler class
@Override
public void handle(RoutingContext routingContext) {
    HttpServerRequest request = routingContext.request();
    request.pause();
    // ...
        authProvider.authenticate(credentials, res -> {
            if (res.succeeded()) {
                routingContext.setUser(res.result());
                request.resume();
                routingContext.next();
            } else {
                routingContext.fail(401);
            }
        });
    // ...
}

It seems to be the right way to do, isn't it?

In addition, I will try the vertx-auth 3.2 solution next week.

Again, thank you for all the informations!


Le vendredi 23 octobre 2015 10:24:41 UTC+2, Cyril Delmas a écrit :
Reply all
Reply to author
Forward
0 new messages