Skip to main content

Dynamic RBAC Authentication

Arkos offers a Dynamic RBAC (Database-Based) system, ideal for applications that require flexible, runtime-configurable roles and permissions. This approach allows you to define and modify access controls directly through your database, making it perfect for larger applications where permission structures may evolve over time.

Key Concepts

  • Database-Driven Permissions: Unlike Static RBAC, permissions are stored in the database and can be modified at runtime without code changes.
  • Required Models: The system uses dedicated models (AuthRole, AuthPermission, UserRole) to manage roles and permissions.
  • Resource-Action Control: Permissions are defined as combinations of resources (models) and actions (view, create, update, delete).
  • Model-Specific Public Routes: You can still configure which routes are public using auth config files as Static Authentication, but role-based access is managed in the database.

How It Works

  1. Required Models Setup: You must implement specific models (AuthRole, AuthPermission, UserRole) in your Prisma schema.
  2. User Association: Users are associated with roles through the UserRole relation model.
  3. Permission Checks: Arkos automatically checks permissions from the database when requests are made.
  4. Public Routes: Configure authentication requirements (public vs. authenticated) through auth config files same as Static Authentication.

Setting Up Authentication Mode To Dynamic

// src/app.ts
import arkos from "arkos";

arkos.init({
authentication: {
mode: "dynamic",
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN,
cookie: {
secure: process.env.JWT_COOKIE_SECURE === "true",
httpOnly: process.env.JWT_COOKIE_HTTP_ONLY !== "false",
sameSite:
(process.env.JWT_COOKIE_SAME_SITE as "lax" | "strict" | "none") ||
undefined,
},
},
},
});
tip

You can pass these values directly in your configuration, but it's a best practice to use environment variables. Arkos will automatically pick up the environment variables even without explicit configuration.

JWT Configuration Options

You can pass these options in your configuration (they override environment variables if both are set):

OptionDescriptionEnv VariableDefault
secretThe key used to sign and verify JWT tokens. Required in prodJWT_SECRET
expiresInHow long the JWT token stays valid (e.g., '30d', '1h')JWT_EXPIRES_IN'30d'
cookie.secureIf true, the cookie is only sent over HTTPSJWT_COOKIE_SECUREtrue in production
cookie.httpOnlyIf true, prevents JavaScript access to the cookieJWT_COOKIE_HTTP_ONLYtrue
cookie.sameSiteControls the SameSite attribute of the cookieJWT_COOKIE_SAME_SITE"lax" in dev, "none" in prod

Using Env Variables Instead

// src/app.ts
import arkos from "arkos";

arkos.init({
authentication: {
mode: "dynamic",
},
});
JWT_SECRET=my-jwt-secret
JWT_EXPIRES_IN=30d
JWT_COOKIE_SECURE=true
JWT_COOKIE_HTTP_ONLY=true
JWT_COOKIE_SAME_SITE=none
danger

Only activate authentication after defining your required models and creating at least one user with isSuperUser set to true. By default, Arkos will require authentication for all endpoint routes and will only allow super users to operate unless you define public routes using auth configs.

Required Database Models for Dynamic RBAC

To implement Dynamic RBAC, you need to define the following models in your Prisma schema:

model AuthRole {
id String @id @default(uuid())
name String @unique
permissions AuthPermission[]
users UserRole[]
}

// This enum options must be in lower-case as Arkos expects
enum AuthPermissionAction {
view
create
update
delete
}

model AuthPermission {
id String @id @default(uuid())
resource String // Database models name in kebab-case
action AuthPermissionAction @default(view) // When using sqlite, this can be a plain String
roleId String
role AuthRole @relation(fields: [roleId], references: [id])

@@unique([resource, action, roleId])
}

model UserRole {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
roleId String
role AuthRole @relation(fields: [roleId], references: [id])

@@unique([userId, roleId])
}

model User {
id String @id @default(uuid())
username String @unique
password String
passwordChangedAt DateTime?
lastLoginAt DateTime?
isSuperUser Boolean @default(false)
isStaff Boolean @default(false)
deletedSelfAccounAt DateTime?
isActive Boolean @default(true)
roles UserRole[] // Association with roles through UserRole model
// other fields for your application
}
tip

You can add fields like createdAt, updatedAt, updatedAt and other fields all according to your application requirements. For fields that are not required by Arkos you can interact through the interceptor middlewares. see Authentication Interceptor Middlewares and see General Interceptor Middlewares.

Managing Authentication For AuthRole, AuthPermission, UserRole

Treat them as normal prisma models although these are used for authentication by Arkos, define the authentication also through database and make it private or public through auth config files (Most of the time will be private, maybe never public).

Understanding the Dynamic RBAC Models

AuthRole

  • Represents a role that can be assigned to users
  • Contains a unique name identifier
  • Links to permissions and users through relationships

AuthPermission

  • Defines what actions can be performed on specific resources
  • resource: Corresponds to your Prisma model names in kebab-case (e.g., user, blog-post)
  • action: Must be one of the lowercase values: view, create, update, delete
  • Special resource file-upload is available for file upload permission control (see File Uploads Authentication)

UserRole

  • Junction table connecting users to roles (many-to-many relationship)
  • Allows a user to have multiple roles or you can change to be only one
  • Allows roles to be assigned to multiple users

User

  • The User model remains similar to the Static RBAC version
  • Instead of having a direct role or roles field as Enum or String, it relates to roles through the UserRole model
  • You can also change between roles and UserRole[] to role and UserRole to use single based role

Understanding The User Model Fields

The User model fields serve the same purposes as in Static RBAC:

id: String

  • Unique user identifier, typically a UUID

username: String

  • Primary login identifier with unique constraint
  • Can be changed to others like email and customized to work in authentication via the login.allowedUsernames option
// src/app.ts
import arkos from "arkos";

arkos.init({
authentication: {
mode: "dynamic",
login: {
allowedUsernames: ["email"],
}, // Use email field for authentication
// other configs
},
});

Dive deep about allowed username fields and also see how to login with nested fields from user model on this guide Example Changing The Username Field Guide.

password: String

  • Stores the hashed user password

passwordChangedAt: DateTime?

  • Used to invalidate JWT tokens after password changes
  • Allows to prevent JWT tokens generated before not to work

lastLoginAt: DateTime?

  • Tracks the user's most recent login time

isSuperUser: Boolean

  • When true, grants full system access regardless of roles or permissions
  • Reserved for system administrators

isStaff: Boolean

  • Indicates users who can access admin areas in your frontend
  • Does not directly affect backend permissions (just recommended)

deletedSelfAccounAt: DateTime?

  • Tracks if/when a user has voluntarily deleted their account
  • Supports soft deletion functionality

isActive: Boolean

  • Controls whether the user can access the system
  • When false, prevents all API actions

Creating Auth Configs for Dynamic RBAC

With Dynamic RBAC, auth config files serve a more limited purpose - they only control which routes require authentication (public vs. private). The role-based permissions are managed in the database.

// src/modules/post/post.auth-configs.ts
import { AuthConfigs } from "arkos/auth";

const postAuthConfigs: AuthConfigs = {
authenticationControl: {
view: false, // Public endpoint: no authentication required to view
create: true, // Authentication required to create (default behavior)
update: true, // Authentication required to update (default behavior)
delete: true, // Authentication required to delete (default behavior)
},
// accessControl is not used in dynamic mode since permissions are in the database
};

export default postAuthConfigs;

Explanation:

  • ✅ authenticationControl: Determines which actions require authentication. Setting an action to false makes it publicly accessible.
  • ❌ accessControl: Not used in Dynamic RBAC mode as permissions are managed in the database.

Checking Available Resources

Arkos provides an endpoint to help frontend developers retrieve the list of all available resources when building permission management interfaces:

GET /api/available-resources

This endpoint returns a list of all resources (model names) available in your application that can be used when creating permissions. For more details, see Checking Available Resources Guide.

Benefits of Dynamic RBAC:

  • Runtime Configuration: Permissions can be modified without code changes or redeployment
  • Flexible Permission Structure: Easy to adapt to changing business requirements
  • Centralized Management: All permissions are stored in one place (the database)
  • UI Integration: Build admin interfaces to manage roles and permissions
  • Scalability: Well-suited for large applications with complex permission needs

When to Use Dynamic RBAC:

Dynamic RBAC is ideal for:

  • Large applications with evolving permission requirements
  • Systems where non-developers need to manage permissions
  • Applications where new resources are frequently added
  • Multi-tenant systems with custom role configurations

Future Enhancements

In upcoming releases, Arkos will support:

  • Row-Level Policies: Define permissions at the database row level (e.g., authors can only edit their own posts)
  • Advanced Permission Rules: Create more complex permission logic beyond basic CRUD operations

Now you can start sending requests to the authentication endpoints. Read Sending Authentication Requests Guide to learn more.