PACT - Contract Testing: dealing with authentication on the Provider
PACT is a tool for Contract Testing — a type of integration testing that allows you to ensure services can communicate with each other. This blog post will go a little bit in-depth about dealing with authentication when doing provider verification, so it assumes you have some knowledge of contract testing already. If you want to learn more about it, I highly recommend you visit https://docs.pact.io/ — the official PACT documentation.
At DAZN we have a micro-services architecture on the backend, and a similar concept called micro-frontends for, well, the frontend. As such, Contract Testing seemed like quite the natural candidate for us. A problem we came across relatively quickly was how we were going to deal with authentication. The flow is: consumer writes tests that define interactions, a contract gets created and published, and then that contract gets replayed against the provider. Depending on the type of authentication of your provider, you may come across some issues when replaying the request against the provider.
Think about it. If your provider has an authentication header that requires some sort of token, for example, then when pact reads the consumer contract for verification on the provider’s side, it will need to have a valid token; if that token had been persisted in a pact contract, it probably has expired by now.
The problem is: when your consumer writes the contract, it cannot accurately represent the authentication part of your provider.
How can we solve this?
customProviderHeaders — Headers for all requests
At the time, this was the only thing that was offered by PACT. They provided you with a way to inject a static header onto every request that would be replayed by the provider. You add a customProviderHeaders key on the options you pass to verify your pacts, with an array of the various headers you want to insert as the value:
While this may solve the problem for certain types of authentication, it didn’t fix it for us. We needed a way to dynamically create authentication based on certain properties of the request, since for this particular service, the requests were using AWS signing. This sort of authentication is dynamically generated from several details of the request, such as the host, path, method and AWS region.
Our original solution
We first contacted the PACT developers about this on their slack channel. At the time, PACT did not support a way to dynamically create this authentication (i.e. have awareness of what request it is replaying at the time that it is injecting headers). They asked me to open a feature request on their Github page issues.
In the meantime, I needed a workaround! What we decided to do was to stop fetching the contracts directly from the Pact Broker, and instead do it from a local folder (in the CI, of course). Then, we would be able to fetch the contracts from the Pact Broker ourselves in a Before hook, parse them, and modify them dynamically by adding the correct headers for each request we looped through. I won’t go into much more details about how we implemented this, because, as I will explain after, the PACT team has solved it now and we no longer need this workaround. I’ll leave you with a bit of code that contains the skeleton of our approach (compared with the one above):
Where generateTempPact was the function that got the pact from the Pact Broker, parsed it, dynamically inserted the headers, and then put it in the path we specified in the provider options.
PACT Team to the Rescue! — the better solution
As I mentioned, they asked me to post a feature request on their Github issues. And they actually got around to it! I was very happy with the type of open communication their developers have. Not only did they respond quickly, but they also eventually implemented the solution to the problem!
The approach they took, which I quite like, is to introduce the concept of requestFilters that gets defined in the provider options. A request filter is a function that has access to the req and res objects from the contract file, and can modify them before passing them to be verified. This is exactly what we needed for this problem. It is a much cleaner solution that solves other similar issues we may have in the future where request/response interception may be useful. You can see their example of it on their Github.
Here’s how our previous provider.spec.js would now look like:
Our generateNewHeaders function would now take care of creating the required AWS headers, and adding them to our existing ones.
It’s also worth noting that, for this release, the PACT team also added a new feature of being able to specify a stateHandlers object in the options. This object consists of key-value pairs, where the key is the providerState you specified in your contract, and the value is a function that performs the actions needed in order to achieve that state. This is a great improvement on the previous way of doing it, as you needed to have an endpoint available that performed the action for you. You can see this implemented in the same example I posted above.
All of this is now available in PactJS versions 8.x.x.
The PACT team really did a great job in delivering a missing feature, and making their tool more and more useful. The ability to manipulate requests before provider verification is a key requirement for certain end-to-end contract verification flows, and when combined with the new provider states, it really adds a lot to the tool.
But remember: while these are really useful additions, you have to be careful when using them. As the PACT team advises, you should be weary with messing too much with consumer contracts at the verification level — you still want your contract to represent what your consumer team was expecting!