Intermediate workshop
NestJS Workshop: Building for Production
Mastering NestJS: From Basics to Advanced Application Design
Ory offers excellent documentation but needs more support tools and in-depth examples of using its libraries in TypeScript and NestJS projects. I decided to contribute to it by creating a set of libraries to interact with APIs, which will (hopefully) make integration into your NestJS project easier. This post presents the ideal use case to divulge my routines for creating libraries in NestJS/Nx!
Feel free to skip the journey and go straight to the code!
Note: This article is part of a series on integrating Ory in production with NestJS. If you are interested in the other articles, you can find them here.
Intermediate workshop
Mastering NestJS: From Basics to Advanced Application Design
I will follow the recipe from a previous blog post to create the libraries.
This workspace is composed of three public libraries (at least for the moment) and one private library:
kratos-client-wrapper is a set of NestJS modules that wraps @ory/client and, more particularly, the Frontend and Identity APIs, which are part of Ory Kratos
keto-client-wrapper is also a set of NestJS modules that wraps @ory/client's Permission and Relationship APIs, which are part of Ory Keto
base-client-wrapper is an internal NestJS module that provides a base class that the Ory client wrappers can extend (similar to the BaseApi
keto-relations-parser is a node library that allows manipulating relations tuples using Ory Permission Language notation. This library is an improved version of this existing lib.
Edit: Recently, I created three extra packages,
and keto-cli
, to interact with the Ory APIs from the command line and kratos-cli
. They are not covered in this article. hydra-client-wrapper
Now, let’s focus on the Ory API integration. Ory already provides an auto-generated client based on their Open API specifications, which uses axios under the hood to send HTTP requests. How can we make this an even better experience for NestJS users? I would say:
Easily importable and configurable module and services
Mockable dependencies for testing
Module and services with clear boundaries and simple interfaces
Error handling that is consistent across all modules and services
A way to automatically retry requests in case of rate-limiting
NestJS Guards to check user sessions and permissions on endpoints
Ory services usually have a public API and an admin API.
Ory Kratos' public API allows users to register accounts, log in, and check user sessions. On the other hand, the admin API enables the management of users (called identities in Ory), authentication methods, and sessions. Ory Keto's public API allows end users to check permissions while the admin API manages relationships between entities (called namespaces in Ory).
A logical path to split our modules is to follow this organization; it would result in the following:
Ory service | Library | Public API | Admin API |
---|---|---|---|
Ory Kratos | kratos-client-wrapper | OryFrontendModule | OryIdentitiesModule |
Ory Keto | keto-client-wrapper | OryPermissionsModule | OryRelationshipsModule |
As said previously, I wanted to easily mock dependencies for testing; moreover, to improve error response handling for calls to Ory APIs, providing a custom axios instance can be helpful.
Luckily, the BaseApi class from @ory/client
HttpService
@nestjs/axios
HttpService.axiosRef
At the end of this article, the dependency graph will look like this:
Note: This graph was generated using Nx Graph CLI.
The starting point is the generic module, OryBaseModule
OryBaseService
To configure and implement the retry logic, I needed to extend the AxiosRequestConfig
axios
OryAxiosRequestConfig
OryBaseModuleOptions
IOryBaseModuleOptions
OryBaseModule
kratos-client-wrapper
keto-client-wrapper
To make the axios
AxiosRequestConfig
To improve error handling on the consumer side. I created an OryError
Error
AxiosError
OryError
AxiosError
OryBaseService
Ory
HttpService
axios
axios
The OryBaseModule
DynamicModule
forRoot
forRootAsync
HttpService
OryBaseService
With the forRoot
HttpModule
OryBaseService
HttpService
With the forRootAsync
HttpModule
HttpService
OryBaseModuleOptions
Tip: Have a look at the NestJS documentation to know more about dynamic modules.
For this package, I created two modules (see Module organization), both of which consume the OryBaseModule
Interfaces for these modules (OryIdentitiesModuleOptions
OryFrontendModuleOptions
OryBaseInterface
There is nothing new here. The setup is very similar to the OryBaseModule
OryBaseModule
OryIdentitiesService
OryFrontendService
Both services will depend on OryBaseService
IdentityApi
FrontendApi
To make it easier to protect endpoints with Ory Kratos, I created a Guard that depends on OryFrontendService
I wanted to be sure that the Ory clients use the custom Axios instance with the options provided by the OryBaseModule
OryBaseService
As said earlier, the original goal was to improve the original library that allows parsing relation tuples with the following changes:
Replace Antlr4 based parser with a simpler and more efficient Regex parser (it still needs to be tested in some edge cases)
Create a fluent API to construct a relation tuple object
Provide a set of helpers to convert RelationTuple to Ory Keto API parameters
After a quick discussion with the author of the original library, it seemed like the Antlr4 parser was a bit overkill for the use case and that a more straightforward Regex match could be enough. The first task was to write a Regex that matched the Ory Permission Language notation. Here are the test cases I used to validate the Regex:
And the Regex is brought to you by OpenAI:
Please let me know if there is a better way to write this Regex!
Another discussion with some co-workers made me realize the importance of providing a fluent API to construct the relation tuple. Using the notation supplied by the Ory Permission Language, it was hard to verbalize the relationships. This results in the following API:
Finally, I provided a set of helpers to convert the RelationTuple to Ory Keto API parameters; this API seems to be a bit inconsistent in the way it expects the parameters, so I tried hiding it by making the RelationTuple interface the single model to interact with.
You can find those helpers here.
The approach is the same as for the kratos-client-wrapper
OryPermissionsModule
OryRelationshipsModule
For this Guard case, things are more complicated than the OryAuthenticationGuard
keto-relations-parser
Note: Want to know more about this notation and how it works? Check out the Zanzibar Academy.
The relation tuple(s) must be dynamically constructed with the API endpoint parameters and passed to OryPermissionService
OryPermissionChecks
Note: The permissions to checks can be composed of multiple relation tuples and multiple conditions. The conditions can be nested and combined with
or AND
logical operators. This allows for a high level of expressiveness when defining permissions. OR
The Guard is a mixin that depends on:
OryPermissionChecks
OryPermissionService
The most important part of the Guard is the evaluateConditions
Note:
The unit tests for the Guard here, can give you a better understanding of how it works.
Hopefully, this implementation could be improved when Keto supports checking multiple relation tuples in a single request, which is apparently a work in progress.
To ensure that the libraries work well together and that the Guard is successfully activated to protect endpoints, I created the following setup:
Custom Docker images for Kratos and Keto
A mocked application that uses the libraries
Some end-to-end tests using NestJS testing utilities and Jest
The setup refers to the keto-client-wrapper
kratos-client-wrapper
I created a Dockerfile for each service. The Dockerfile for Keto is simple; it copies the configuration and the namespaces files, and sets the health check and the command to run the service.
Note The health check will help us ensure the container is ready to accept requests before running tests
Then, we can add an Nx target to make publishing to GHCR easy:
Finally, the image is published with nx run keto-client-wrapper:docker-push
Here is a simple Controller
In this test, I create a relation (owners
User
Toy
Note: Before starting the tests and when running locally, Ory services are started with
with the docker compose
option to ensure services are up and running and stopped after the tests are done. In CI, the services are already running, so the tests can be run directly. wait
I hope this article will offer a better understanding of Ory APIs and concrete interactions with NestJS. The libraries are available on GitHub.
In this series's next article, I will show you how to integrate the libraries into a NestJS application and configure Ory Kratos and Ory Keto in a production environment. Stay tuned! 🚀