At work, we recently discussed how best to authenticate an API endpoint that we needed from a rails engine. In the host application, we require that clients use tokens for API authentication. In a parent class before filter, we call a remote web service to validate these tokens. We all agreed that it was a bad idea to copy this code over from the host into the engine.
There were a couple of reasons for this. Not only would the code be duplicated, but we felt like it would be a better separation of concerns to keep authentication out of the engine. Another argument is that we would not want to impose an authentication system from the gem on the host application. Ryan Bigg expands on this in a blog post on Forem.
We investigated a handful of approaches. The simplest, least invasive approach seemed to be to use Rack middleware. First, we register the middleware with an array of endpoints to validate:
Then we define the middleware:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
In most cases, we will just end up executing calling the host application (via
@app.call(env)). However if the
PATH_INFO matches an initialized endpoint and we pass in an invalid token, then we will return a
401 HTTP status
There are a few drawbacks to this approach:
- We do not take into the HTTP verb into account and may match more endpoints than we intend (e.g. api/accounts/:account_identifier)
- The middleware runs for all requests, even ones where we have a separate authentication mechanism