atuttle / Taffy

:candy: The REST Web Service framework for ColdFusion and Lucee
http://taffy.io
Other
226 stars 118 forks source link

Auth filter (Middleware) for a set of end points #392

Open shakeelosmani opened 4 years ago

shakeelosmani commented 4 years ago

We are a large CF shop and we use Taffy as our web service framework of choice. We also have integrated lots our own code in application.cfc by extending the Taffy Application.cfc. However as of now the authentication mechanism is fairly straight forward if you have some sort of session we allow Taffy to serve secure resources (Which works fine in most cases), but there are certain secure resources which should be controlled in a more granular way. If the authentication presented have a certain value and that value does not correspond to expected value we want to return 401. However this type of checking is applicable for only a set of endpoints that exist together say in a sub folder inside a resource folder. We also do .net and with .net MVC we do this little differently using middleware that intercepts a request url and if request url at root /{some-endpoint-root} resource then we can do any kind of filtering we need and return 401 if the request does not meet the filters. I know application cfc can be piggy backed but is there a more cleaner middleware like setup that can be achieved. I am thinking of onTaffyRequest can you read some metadata like taffy:filter="a path to a cfc" and then that cfc code gets executed and return 401 or lets continue the request. We also do not want to modify the core of the framework, so that upgrade path is cleaner

atuttle commented 4 years ago

I'm fine with the concept of adding useful functionality to the framework, but I don't see exactly how what you're proposing would work. It sounds very specific to your need, and as a rule of thumb I'd be more interested in a general solution.

I saw your discussion on the CFML Slack about this issue, so I know that you've already seen some of this stuff...

My gut reaction is that you should add metadata to your resources and/or handler methods as appropriate, and then check that metadata in onTaffyRequest. You could generalize that down to something like...

function onTaffyRequest(verb, cfc, requestArguments, mimeExt, headers, methodMetadata, matchedURI){
    if ( !allowContinue( arguments.cfc, arguments.methodMetadata ){
        return noData().withStatus(401);
    }
}

// define your own implementation of allowContinue() here in Application.cfc

In my opinion, that would seem terse enough to be useful in Application.cfc. But if you have reasons why this isn't acceptable, I'd be interested to hear them.

And here's a random closing thought: What if each resource CFC could also implement some sort of allow function that would accept useful metadata about the request and return true if the request should continue, or a representation instance (e.g. return noData().withStatus(401);) if the request should not continue. The framework could detect that the allow function exists, and make it some sort of phase 2 of the onTaffyRequest step. That would allow you to implement different rules on a CFC-by-CFC basis, if that would somehow be useful. The idea still needs more thought behind it, but is that kind of what you're aiming at?

shakeelosmani commented 4 years ago

@atuttle thanks for you response, as of now we have kind of at application cfc level did some interception based on some security hash param that we set and moved on. However I still have reasons to why not to do it at application cfc. If we continue to do all sort of checking at application cfc the amount of code that would be written there would start to become more and more unmanageable as different types of middlware requirement increase. In this instance we just had an extra authorization check on top of authentication, tomorrow morning a different business logic may need to run before invoking the webservice resource, as of now I know people tend to run them within the methods in the endpoint first and then do the endpoint result processing. However this is not clean and is prone to risk which I discuss later. A cleaner approach in my opinion is a pluggable middleware code that runs at the resource level, that way a developer accidentally does not check to fail for say post or put method where as he did take care of it at the get level. Think of HttpInterceptors in angular (Not a fan of this how Angular did it, they clone the request and add the extra stuff and send it, but that is just my opinion) or the whole middle ware architecture exposed through ASP.net mvc core (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1) -- This is my opinion is a better approach even Spring does this by intercepting underlying servlet -- https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-handlermapping-interceptor . I personally feel this would be a great enhancement. In fact your random closing thought sounds exactly like what I am talking about, If we can come to an agreement on an architecture then we all can contribute and finish it up. By the way we bought your book and keep it as a quick Taffy reference for all our devs. Kudos for that :)

atuttle commented 4 years ago

Sounds like we're moving toward something mutually agreeable. Here are my thoughts in no particular order:

shakeelosmani commented 4 years ago

I like the idea. I wanted to add we could make it even more generic by allowing a callback function that the allow method (or we can name anything) can read from the resource and execute. If any resource wants the interceptor (allow or can be called anything) to run the callback then the resource must be decorated with that agreed name like forexample allowCallBack=true, callBackName=xxx. Then the return signature of the call back can be a boolean /struct that then tells the request to continue or stop. of course all request params and everything should be available to callback function so that it can run its business logic. If I am not thinking this through right let me know.

atuttle commented 4 years ago

I'm not really following on that part. Can you give a pseudo-code example to illustrate the idea?

shakeelosmani commented 4 years ago

Sure here you go:

image