1

With koa.js and koa-passport, can I apply middleware conditionally?

My server authenticates web visitors using koa-passport with the built-in 'session' authentication:

app.use( passport.initialize() );
app.use( passport.authenticate( 'session', {} );  // = app.use( passport.session() ); 

Now I need to access the server as an API end point, also via the SSL port. An API end point doesn't use sessions; authentication is by bearer token. Passport-http-bearer does this:

app.use( passport.initialize() );
app.use( passport.authenticate( 'session', {} );
app.use( passport.authenticate( 'bearer', {session:false} ));

This doesn't work, of course, because either 'session' or 'bearer' will always fail, so no request gets through.

I thought of applying the middleware conditionally:

if( this.request.headers.authenticate.startsWith( 'Bearer' )){
    app.use( passport.authenticate( 'bearer', {session:false} ));
} else {
    app.use( passport.authenticate( 'session', {} );
}

That doesn't work, of course, because the stack is not built for each request at runtime, so the request headers are undefined and it won't compile.

I tried koa-unless; in theory it should work, but even the sample code throws an error on koa@1.2.

So at the moment I have the condition patched into the passport-http-bearer strategy; this works, but it's pretty much unsupportable.

Any ideas? Thanks!

HieroB
  • 3,917
  • 4
  • 17
  • 22
  • Perhaps create two instances of `app`, one for regular/initial server requests and the other instance for API endpoints. This way each `app` could have entirely separate sets of rules and middlewares associated with them. Then mount the routes accordingly depending on the type of request received. – Alexei Darmin May 21 '16 at 18:10
  • Thanks Alexei, I was hoping to avoid building 2 servers--since they both require SSL on port 443 they would have to run on separate machines. I think overall it would require quite a bit more support. – HieroB May 28 '16 at 11:33

2 Answers2

0

I personally haven't used the bearer before but you should be able to use the isAuthenticated helper method that is available in your context.

Here's another stackoverflow thread Documentation for "ensureAuthentication" "isAuthenticated" passport's functions?

Koa

exports.isAuthenticated = async (ctx, next) => {
  if (ctx.isAuthenticated()) {
    await next()
  }

  if (!ctx.isAuthenticated()) {
    ctx.session.returnTo = ctx.request.originalUrl
    ctx.redirect('/auth/login')
  }
}

If that doesn't work, you can create a custom middleware to handle both conditions. Passport.js optional authentication

exports.isAuthenticated = async (ctx, next) => {
  if (ctx.isAuthenticated() || await passport.authenticate('bearer', {session: 'false'} ) ) {
    await next()
  }

  ctx.session.returnTo = ctx.request.originalUrl
  ctx.redirect('/auth/login')
}
Community
  • 1
  • 1
uptownhr
  • 662
  • 6
  • 15
  • Thanks uptownhr, yes, I'm using isAuthenticated, but it only works after the client passes one of the two authentication methods early in the middleware stack. The problem with running two authentication methods is that one will always fail, so isAuthenticated() always returns false. So I run the session authentication first--it passes or fails--then I run the bearer-token authentication, but I've modified it so it only runs if there is a token in the headers; otherwise it doesn't do anything. This works, but it would be better if I didn't have to modify the module. – HieroB May 28 '16 at 11:49
  • That is true but i guess my point was that you don't need to put these on the top of your app. You can apply them as you need, where you want them. When using koa, I always use it with a combination koa-router, which allows you to have multiple middleswares per path. – uptownhr Jun 02 '16 at 08:59
  • But going back to the custom middleware solution, the second solution. You can use this middle ware in place of your 2 app.use. This will check both your session and heaer, and only if both fails cause the request to fail. In my example, i'm redirecting to /auth/login but you can handle the failure as you please. – uptownhr Jun 02 '16 at 09:02
-1

Not sure if is applicable but you can kinda do conditional middleware by doing ternary like this

koa.use(koa.context.something ? (ctx, next) => next() : passport.initialize())

So in your case this should work

app.use(this.request.headers.authenticate.startsWith('Bearer') ? 
  passport.authenticate('bearer', {session:false})) 
  : passport.authenticate('session', {})
)
Joel Yek
  • 7
  • 1