Build a Multi-Tenanted, Role-Based Access Control System TomDoesTech・92 minutes read
The text provides a detailed guide on creating a multi-tenanted role-based Access Control authentication API using Neon's PostgreSQL service with various technologies and steps, covering application creation, user registration, login, role assignment, and permission checking. Key points include data structures, setting up the database, creating roles, defining permissions, user authentication, and implementing security measures through user roles and permissions.
Insights The tutorial focuses on creating a multi-tenanted role-based Access Control authentication API using technologies like Drizzle ORM, Fastify, PostgreSQL, and TypeScript. The process involves setting up databases, generating migrations, creating roles, handling user permissions, and implementing authentication for enhanced security. Key steps include defining schemas, establishing routes, creating roles, assigning permissions, and testing the authentication system to ensure secure user access to applications. Get key ideas from YouTube videos. It’s free Recent questions How can I create a role-based access control system?
By defining roles, permissions, and assigning roles to users.
Summary 00:00
"Multi-tenanted Role-Based Access Control API Tutorial" Video tutorial on creating a multi-tenanted role-based Access Control authentication API Sponsored by Neon, providing PostgreSQL as a service with a generous free tier Features to be built: application creation, user registration, login, role creation, role assignment, permission checking Technologies used: Drizzle ORM, Fastify for API layer, PostgreSQL with Neon, TypeScript Requirements to follow along: editor (VS Code), Node.js, database (Neon), REST client (Thunderclient or Postman) Learning outcomes: creating multi-tenanted applications, role-based access control systems, relational database concepts, backend service file structure, TypeScript, Fastify, RESTful API design, Drizzle ORM Data structure: application linked to users, roles specific to applications, permissions specific to roles Example of multi-tenanted application like Shopify, where users can be registered under different application IDs Data flow: request to route, controller for business logic, service for database requests, structured for easy metric additions Helpful files in GitHub repository for commands and API collection for Thunderclient or Postman usage. 15:21
Setting up Neon database with PostgreSQL and Drizzle. Sign up to Neon and create a new database by visiting neon.tech. Use the provided link in the description to access Neon's website. Click on "Sign in" and log in with GitHub. Create a new project named "user API" and select the region closest to you, such as Singapore. Choose the PostgreSQL version, like version 15. Copy the database connection and paste it into a new file named ".env". Define the database connection inside the ".env" file. Utilize the Pino logger to debug and redact sensitive information like the database connection. Explore Neon's features like branches, tables, SQL editor, and database operations for debugging. Set up the database using PG and Drizzle ORM to create schemas for applications, users, roles, and a join table. Generate migrations for the schemas using Drizzle and run them to create the tables in Neon. Ensure to run the migrations before proceeding to avoid issues with indexes and table relationships. 32:49
"Composite Primary Key for Users and Roles" Role ID is a UID referencing roles.ID, set as not null. User ID is a UUID referencing users.ID, also set as not null. These properties don't need to be unique individually but must be unique together. A composite primary key named "users to roles" is created with three properties: application ID, role ID, and user ID. Running "npm migrate" results in the creation of four tables. Migrations should be run before creating relationships to avoid issues with unique indexes. After starting the application, all necessary tables are created for writing application code. Modules for applications and roles are created in the source directory. Files for routes, controllers, services, and schemas are set up for the applications module. Permissions and roles are defined for creating default roles like super admin and regular user. 51:15
"Optimize roles, permissions, and user creation" Create a super admin role inside the application controller by using `const super admin role = await create role(application.ID, 'system roles.application.superuser', all permissions)`. Adjust the permissions array by casting it to `as unknown` and then `as array of string` to resolve TypeScript errors. Establish an application user role with `const application user role = await create role(application.ID, 'system roles.applicationuser', user role permissions)`. Rename `user role` to `user role permissions` and ensure correct import. Implement a promise optimization by using `const array = await Promise.allSettled([promise to create super admin role, promise to create application user role])`. Access the values of the promises using `super admin role.value` and `application user role.value` after checking for potential rejection. Resolve Fastify errors by ensuring routes are async functions when using async code. Develop a handler to list applications by creating `export async function get applications Handler` and linking it to the route. Begin creating a user module by setting up folders for controllers, routes, services, and schemas within the users module. Define a service to create a user with hashed passwords, a schema for user creation, and a controller handler for user creation with role assignment based on initial user status. 01:10:51
User Creation and Role Assignment Process To handle a specific scenario, a message is set to be returned with a code of 404 if a certain condition is met. The creation of a user is initiated, with a try-catch block to handle any errors that may arise during the process. A separate service is required to assign a role to the user, involving the insertion of data into a specific table. User routes are defined within the application to facilitate user-related functionalities. A request to create a user is made, specifying details such as email, password, name, and application ID. An issue arises when attempting to create an initial user due to a misconfiguration in the setup. Verification of user data in the database is conducted to ensure correct storage and retrieval. The process of logging in is established, involving the creation of a login handler and associated schemas. User permissions are retrieved and consolidated to provide a comprehensive view of the user's roles and access levels. A refinement in the data structure is implemented to enhance readability and efficiency in handling user permissions. 01:30:46
"User Role Creation and Permission Management" The function involves omitting a property called "permissions" from a specified result type, which is a set of strings. To select specific properties from a user table, an object is created with ID, email, name, application ID, role ID, and password. The permissions from roles are merged into a set, condensing them for each user role. The user's permissions are transformed into an array to avoid duplicate permissions. A token is generated with user permissions and ID for authentication purposes. The process of creating a role involves defining a schema with name, permissions, and application ID, ensuring only allowed permissions are used. A handler is created to execute the role creation process, returning the created role. Routes are established for role creation, utilizing the defined schema and handler. A user can be assigned a role by specifying the user ID, role ID, and application ID, with error handling in place. A guard is implemented to restrict role creation and assignment to users with specific permissions, enhancing security measures. 01:51:34
"Secure user authentication with Fastify and Thunderclient" To set up user authentication, start by assigning null to the user object, which is an object type, and then define a type for the user with an ID as a string and an array of scopes. Declare the user type in the Fastify request module to inform Fastify that a user will exist on the request, resolving TypeScript errors by specifying the decoded string as a user type. Implement guards by using app.guard.scope in the user routes and roles, defining the necessary permissions for each route and fixing TypeScript errors by adding generics for body types. Test the authentication system using Thunderclient, create an application, user, and role, ensuring the JWT is correctly set in the authorization header to access permissions. Enhance security by inferring the application ID from the JWT instead of accepting it in the request body, ensuring that users can only perform actions related to their application, and successfully assign roles to users following the updated authentication process.