Adding Features

Real systems in production need more than generated CRUD endpoints. They need security, permissions, asynchronous processing, operational jobs, event publishing, and, in SaaS scenarios, per-tenant isolation.


Lino adds these capabilities as standard features. Each feature updates project metadata, generates source code, adds configuration and keeps backend, frontend, messaging and persistence aligned with the same architecture.

Authentication and authorization

The authentication feature adds the security foundation used by Lino projects to protect APIs, identify users, control access to operations and integrate the same permissions with Blazor Web Apps. It is installed with the command:

lino feature auth add --service <ServiceName> --module <ModuleName>

The command can also be executed interactively with lino feature auth add. The canonical branch is feature auth; The CLI also exposes aliases like feature security and feature identity.

In the current Lino architecture, authentication and authorization rely on asynchronous messaging. The reason is operational: service and module permissions are synchronized by integration events, so the project needs to have messaging enabled before adding security.

What the wizard asks for

The CLI validates the current project, checks if the feature has not yet been installed, asks where the security module should be created and requests the main token settings.

  • Service: service that will host the security resources.
  • Module: target module when the selected service is modular.
  • Access token lifetime: lifetime, in minutes, of the access tokens issued.
  • Refresh token lifetime: lifetime of tokens used to renew sessions.
  • User identifier type: type of data used as a user identifier, such as int, long or Guid.

Generated security model

After confirmation, Lino updates project metadata, creates security configuration values, generates source code files, and runs necessary commands. The generated model includes the central security entities:

  • User stores identities and profile data.
  • Role and role associations to group access rules.
  • Permission and relationships between roles and permissions for granular authorization.
  • UserToken for controlling refresh tokens and token life cycle.

The feature also creates the information needed for security configuration, such as the token signing secret and access token and refresh token lifetimes. Sensitive values are issued as commands to AppHost user secrets, rather than being written directly to versioned files.

Runtime behavior

The API uses JWT Bearer authentication. The user logs in, receives an access token and a refresh token, and starts sending requests to the API with the header Authorization: Bearer <token>. The backend validates the token, loads the user context, and applies authorization rules to the endpoints.

The typical flow remains simple: login via endpoint or dedicated page, issuing JWT with identity claims, roles and permissions, sending the token in each request and validating the signature and expiration through the authentication middleware.

Lino uses policies and permissions to implement granular authorization. Each action may require a specific permission, such as People.Read or People.Create; policies can be configured via AddAuthorization; and endpoints can be protected by declaring .RequireAuthorization.

Generated endpoints and pages use permissions consistently. Backend operations may require permissions, while the generated Blazor UI can load permissions, protect pages, hide unavailable actions, and expose screens for users, roles, permissions, and authorizations when there is a Web App in the project.

When background jobs are already enabled, Lino also adds jobs related to user tokens, such as maintenance routines for disabled or expired tokens.

Background Processing and Outbox

Background processing handles work that should not block the main request and response flow: publishing integration events, cleaning up old records, removing expired tokens, sending notifications, generating reports, synchronizing with external systems, or running scheduled tasks. Lino adds this capability through the background job feature:

lino feature background-job add --service <ServiceName>

The command can also be executed as lino feature background-job add and completed interactively. CLI exposes aliases as job-scheduler and worker.

This feature requires messaging enabled in the project, because its main responsibility is to securely publish messages registered by the Transactional Outbox standard.

What the wizard asks for

  • Service: service in which the background jobs infrastructure will be installed.
  • Background Job library: implementation used for executing and scheduling jobs. The currently generated infrastructure uses Hangfire.
  • Outbox Processing: defines whether the service will register events to be processed by the scheduler.
  • Outbox schedule: interval or schedule expression used to check for new Outbox messages.
  • Outbox batch size: number of Outbox records retrieved and processed per cycle, controlling load, parallelism and resource consumption.

Hangfire Infrastructure

Lino generates the necessary infrastructure to register Hangfire, configure its persistence, expose the dashboard, and schedule the jobs used by the service. The generated configuration includes the information needed to schedule jobs, process Outbox messages, and protect access to the scheduler dashboard.

In generated solutions, the Hangfire schema and tables are part of the application database configuration. The dashboard helps you inspect completed, failed, scheduled, and recurring jobs during development and operation.

Practical benefits include reliable execution of asynchronous tasks, built-in monitoring dashboard, support for recurring or scheduled jobs, and persistence of jobs in the database.

Processing with Transactional Outbox

To ensure consistency in asynchronous integrations, Lino uses the Transactional Outbox pattern. A business use case persists domain changes and records an integration event in the Outbox table within the same transaction. The background job then reads the Outbox table and publishes the event through the configured message bus, such as RabbitMQ with MassTransit when these options have been selected for the project.

  1. A command changes the state of the domain and saves this change in a transaction.
  2. A handler registers an integration event in Outbox, for example an event related to the creation of an order.
  3. The Hangfire job blocks a batch of unprocessed messages and marks these messages as in process.
  4. Each message is deserialized and published by the event bus.
  5. Successful messages are marked as processed; failed messages preserve error information for later analysis or retry strategy.

The result is atomicity between the database and recorded events, reliability so that events are not lost in the event of failure, and scalability so that multiple consumers process published messages.

The generated job also includes routines to remove old Outbox messages that have already been successfully processed and release messages that were stuck in processing, reducing the risk of permanent locks after application failures.

When authentication is installed, the background job feature can also host token maintenance jobs, keeping security data clean over time.

With these additional features, Lino delivers robust security, granular access control, and reliable asynchronous processing for critical requirements of modern production applications.

Multi-tenancy

Multi-tenancy is the feature used when the same application needs to serve multiple clients, organizations or environments while maintaining isolated data, permissions and configurations for each tenant. In Lino, tenant support is added with:

lino feature tenant add --service <ServiceName> --module <ModuleName>

The command can also be executed interactively with lino feature tenant add. The CLI branch also exposes aliases like tenancy.

Prerequisites

Tenant support depends on two architectural capabilities:

  • Authentication and authorization, because user access, roles, permissions and tokens need to be tenant-aware.
  • Messaging, because tenant and user information can be propagated to other modules by integration events.

If these prerequisites are missing, the command is rejected. The feature can also only be installed once per project.

What is generated

Lino creates the tenancy model, configuration, events, API integration, and frontend resources needed to manage tenants consistently.

  • Tenant stores tenant identity, name, slug, status, isolation mode, and connection string reference.
  • TenantBranding stores branding data for specific visual behavior per tenant.
  • TenantDomain maps hosts or domains to tenants.
  • TenantStatus and IsolationMode define tenant lifecycle and data isolation options.
  • The generated configuration supports tenant resolution and data isolation for the project.

Tenant-aware security

Tenancy changes the way authentication and authorization are interpreted. A valid token and permission are evaluated within the context of the current tenant; a user with access in one tenant does not automatically receive access to another tenant. Therefore, tenant support updates authentication flows, refresh token behavior, permissions and references generated for the UI.

In generated Blazor applications, tenant-aware screens can be added for managing tenants and authorization flows. On the backend, generated endpoints can resolve tenant context from configured host, slug, or route behavior, and then apply the tenant identifier in data and authorization decisions.

Data isolation and propagation

Lino supports tenant-aware domain modeling by allowing entities to declare ownership on a per-tenant basis. In SaaS scenarios, this allows business tables to store tenant-specific data while shared modules continue to receive the tenant and user information they need.

The generated architecture uses events and the Outbox pattern to propagate selected tenant or user data between modules without coupling all modules directly to the original Identity or Tenancy tables. This keeps module boundaries clear, while screens, APIs, permissions, and queries continue to operate with tenant context.

After adding tenancy, create and apply migrations for the affected service, review generated secrets and API keys, run AppHost and validate with at least two tenants to ensure that data, users, roles, permissions and business records remain isolated.

  • Validate isolation: create tests trying to access data from another tenant.
  • Review permissions: a user can have different roles in different tenants.
  • Avoid trusting the customer: tenant informed by route, host, slug or header must be compatible with the authenticated user.
An unhandled error has occurred. Reload πŸ—™