On the engineering team here at Nintex, from time to time we come across technical issues that require non-standard solutions. One such issue occurred while working on a microservice in a broader scope of a complex application with many moving parts. It was communication between Single Page application (SPA) frontend and REST API that drew our attention.
The issues stemmed from some frontend data we could validate on the backend. But, this validation needed a solid base, such as a user identifier or an account to access this part of the application. The well-known approach of solving this problem is to use access tokens. JWT (JSON Web Tokens) is a good example, as it is a proven way to deal with sensible information.
Identifying the issue
The authorization process includes getting an access token (JWT) with user identifier as a payload on SPA, then attaching the token to all requests coming through the API that require authorization. Thus, we can trust the information and validate the requests that come inside the token.
Then we came to the actual problem we had to solve: transparent passing of the auth related properties to the business logic. This is a common operation, and the easiest way to do so is to enrich models that we pass through to the API endpoints with the data that comes inside the token payload.
However, the solution to this problem is not as widely documented in IT society as we had hoped. Separate bits and pieces could be easily found, but it was difficult to find the whole solution.
Hopefully, this post will help someone facing a similar issue.
ASP.NET Core framework which we use for this project supports the idea of middleware where the pipeline of request processing can be customized to use logic targeted for your business needs. Out-of-the-box model binding is an awesome example of a robust well-working solution. It should have been easy to adapt to our needs. And it was… partially.
The problem behind model binding
Before going further, let me go into more detail on one of the challenges we were facing. By their nature, ‘Get’ and ‘Post’ requests are passed and processed differently, and the model binding for them is different, as well.
For ‘get’ requests, models provided to endpoints are constructed using simple data conversion binders, and complex objects are constructed out-of-the-box by converting parameters out of the query string one by one.
This is why providing a proper data type for our authorization information (for example a AuthPayload class with user ID embedded into the access token) and combining a data binder together with a data binder provider was enough to solve our problem.
‘Post’ requests (put, delete), however, are placed in the body and processed using a separate mechanism. It depends on the body data type, which is, in the case of application/JSON, is JSON deserializer. To do it correctly out-of-the-box, one needs to provide [FromBody] attribute for the endpoint model parameter.
The body of the ‘post’ request is processed as a whole. Even if it’s used inside of an endpoint model, there’s an authorization-related property that is not processed with the corresponding custom model binder that we used for our ‘get’ request parameters. This is because the body of the request does not contain the authorization information. Rather, it is sent in the request header.
Solving the issue
It is possible to split authorization information by having two parameters passing through to an endpoint. One as a model that binds to a header of a request, and another to the body; but it does not look as solid and nice as the ability to enrich one model, at least in our eyes.
Each endpoint should have these two parameters with code combining them inside the method making code less clean, allowing for mistakes. For the project at hand, we used CQRS architecture where we pass commands and queries as our endpoint models directly from requests. The solution however can be used for other architecture types.
Here are the examples of a query and a command correspondingly:
It appears that we can reuse the standard binding mechanism to run initial binding, ignoring the authorization part, and then pass through auth data from the token into the model.
The solution, thus, looked self-contained with client passing request related data openly, and authorization related in a trusted way.
The next steps
The next issue appeared out of an unexpected place. Our API documentation didn’t need to expose authorization related properties for the endpoint models because they would not be filled by the client application.
In order to hide information about auth related properties for ‘post’-related models, one needs to just include the [JsonIngnore] attribute, because all the binding happens inside the JSON converter.
‘Get’ related models, on the contrary, do not provide such a nice feature. Swagger, which we use for documenting our API, provides a flexible functionality to solve the problem. It can be found here.
The client-side model after implementing the solution looks like this:
And the server-side one looks like this:
The auth payload is securely passed in the auth header of the request, and the actual business-related data is passed either as query parameters for ‘get’ (query) requests, or as body parameters for ‘post’ (command) requests.
Once implemented auth enriching mechanism is hidden from the business logic, the solution allows us to concentrate on delivering business value rather than focusing on resolving various auth side effects.
We hope this explanation helps if you find yourself in a similar situation navigating data validation challenges. If you enjoyed this post, be on the lookout for more posts like it from Nintex Engineering team in the future!