Payroll Engine Security
| ⚡ The Payroll Engine is a backend service and should not be used on the public Internet. |
|---|
Contents
| Section | Description |
|---|---|
| Backend Server Security | Authentication, API Key, OAuth, CORS, Rate Limiting, Script Safety, Tenant Isolation |
| Client Security | HTTPS on localhost |
| Web Application Server Security | User login, authorization, role-based access control |
Backend server security
Authentication
The following authentication modes are available:
None: no authentication (default)ApiKey: authentication using a keyOAuth: Authentication using OAuth
API Key
In the backend server configuration, a fixed key can be stored that must be included in the request header for every endpoint. This mechanism prevents unprotected access to the API.
OAuth
OAuth mode supports the following configuration options:
Authority— the identity provider URLAudience— the expected audience claimRequireHttpsMetadata— enforce HTTPS for metadata discoveryClientSecret— optional client secret
On startup, the backend validates that both the authority and audience are configured. This prevents token confusion when OAuth is enabled but misconfigured.
CORS
Cross-Origin Resource Sharing is inactive by default (no cross-origin requests allowed). When enabled, the following options are available:
| Setting | Description |
|---|---|
AllowedOrigins |
Allowed origin URLs |
AllowedMethods |
Allowed HTTP methods |
AllowedHeaders |
Allowed request headers |
AllowCredentials |
Whether credentials are permitted |
PreflightMaxAgeSeconds |
Cache duration for preflight responses |
Enable CORS only when the backend is accessed from a different origin, such as a separate web application domain.
Rate Limiting
The backend supports rate limiting with two configurable policies:
- Global policy — applies to all endpoints
- Payrun job policy — dedicated limit for the payrun job start endpoint
Each policy is configured with PermitLimit (maximum requests) and WindowSeconds (time window). Rate limiting is inactive by default.
Script Safety Analysis
When ScriptSafetyAnalysis is enabled, the backend performs static analysis of user scripts during compilation. Scripts using banned APIs are rejected:
System.IO— file system accessSystem.Net— network accessSystem.Diagnostics— process executionSystem.Reflection— reflection-based access
This is opt-in via appsettings.json due to the additional compilation overhead.
Database Collation Check
The DbCollation setting (default: SQL_Latin1_General_CP1_CS_AS) is verified on startup before the schema version check. A collation mismatch is reported as a startup error to prevent silent data integrity issues.
Cryptographic Security
Password hashing uses SHA256 with constant-time comparison to prevent timing attacks. Regular expression patterns include a timeout to prevent ReDoS (Regular Expression Denial of Service) attacks.
Tenant Isolation
The Payroll Engine provides a two-layer tenant isolation model: request-level scoping via the Auth-Tenant header, and a server-wide cross-tenant access policy via TenantIsolationLevel.
Auth-Tenant Header
The optional Auth-Tenant header scopes a request to a single tenant. If present, the backend validates that the tenant in the URL matches the header value:
Auth-Tenant: MyTenantIdentifier
The web application sets this header automatically after a tenant switch, unless the server is configured with TenantIsolationLevel.Write — in that case the header must not be sent and is suppressed. See API Usage for details.
TenantIsolationLevel
TenantIsolationLevel is a server-wide policy that controls whether cross-tenant HTTP access is permitted at all. It is configured in appsettings.json and applies to every request via a global filter.
"TenantIsolationLevel": "None"
| Level | Value | Description |
|---|---|---|
None |
0 | Default. Single-tenant mode. The filter is fully transparent — all requests are permitted. No cross-tenant HTTP access. The RegulationShare controller is hidden. |
Consolidation |
1 | Single-tenant HTTP mode. The filter remains fully transparent. Enables ExecuteConsolidatedQuery in report scripts only (not HTTP cross-tenant access). |
Read |
2 | Cross-tenant read access via HTTP. GET requests and semantically read-only POST requests may omit Auth-Tenant. Mutating requests still require it. |
Write |
3 | Full cross-tenant HTTP access. Auth-Tenant header must not be sent — its presence is a configuration conflict and returns 400 Bad Request. |
The filter is only active at Read and Write level. At None and Consolidation the filter is fully transparent — every request passes through regardless of headers. This means standard clients (PE Console, WebApp) that do not send the Auth-Tenant header are never blocked in single-tenant deployments.
| Configured level | Request type | Auth-Tenant header | Behaviour |
|---|---|---|---|
None or Consolidation |
Any | any | Always permitted — filter transparent |
Read or Write |
Any | present + Write level |
400 Bad Request — contradictory configuration |
Read or Write |
Any | present | Always permitted — single-tenant scoped |
Read or Write |
GET, HEAD |
absent | Requires Read level |
Read or Write |
POST [ReadSemantic] |
absent | Requires Read level |
Read or Write |
POST, PUT, PATCH, DELETE |
absent | Requires Write level |
POST actions that are semantically read-only (report execution, case build, report set retrieval) are marked with [ReadSemantic] in the controller.
Conflict rule: TenantIsolationLevel.Write + Auth-Tenant header = 400 Bad Request. At Write level the server is fully open to all tenants — scoping to one tenant via the header contradicts that intent.
RegulationShare visibility: At TenantIsolationLevel.None, the RegulationShare controller is hidden in Swagger and inaccessible. It becomes visible at Consolidation and above.
Regulation Sharing and Consolidation
Cross-tenant report aggregation (e.g. consolidating Country-A and Country-B payroll results into a single cross-country report) is built on top of TenantIsolationLevel.Consolidation and RegulationShare.
Each RegulationShare entry has an IsolationLevel that defines the access granted to the consumer tenant:
| IsolationLevel | Effect |
|---|---|
Consolidation |
The provider regulation is not added as a payroll layer. The consumer tenant's report scripts may call ExecuteConsolidatedQuery to read payroll results from the provider tenant. |
Write |
The provider regulation is available as a full payroll layer in the consumer tenant (default behaviour). |
This separation ensures that consolidation-only shares are never accidentally resolved as calculation layers:
Region-Tenant (consumer)
└── Payroll
└── Layer 1 · CountryA.Regulation (Full share — calculation layer)
└── Layer 2 · Region.Consolidation (report-only, no payroll layer)
RegulationShare entries:
CountryA-Tenant / CountryA.Regulation → Region-Tenant [IsolationLevel: Consolidation]
CountryB-Tenant / CountryB.Regulation → Region-Tenant [IsolationLevel: Consolidation]
Each country regulation exposes WageTypes 7000–7030 with clusters: ["Consolidation"] as the standardised cross-country reporting interface. The report queries these by cluster name — no country-specific mapping is required.
In the report ReportEndFunction, ExecuteConsolidatedQuery automatically iterates all Consolidation-level shares and merges the results:
// Aggregates payroll cost data from all provider tenants (Country-A + Country-B)
var results = ExecuteConsolidatedQuery(
tableName: "PayrollCosts",
methodName: "QueryPayrollResultValues",
mergeColumn: "ResultId",
parameters: new Dictionary<string, string>
{
["Filter"] = "clusters/any(c: c eq 'Consolidation')"
});
The TenantId parameter is overridden per share iteration — the script does not need to know which tenants are involved. New member tenants are added by creating a new RegulationShare entry without touching the report script.
Security guarantee: ExecuteConsolidatedQuery is blocked at the scripting runtime level when TenantIsolationLevel < Consolidation. Even if a RegulationShare with IsolationLevel = Consolidation exists, no cross-tenant query is executed unless the server is explicitly configured to permit it.
API Version Header
Every response from the backend includes the X-Version header, which carries the current API version:
X-Version: 1.0
GET request body
The REST API uses HTTP POST operations for all endpoints that send potentially sensitive data in their requests. This prevents such data from inadvertently ending up in logs, browser history, or the like.
Database SQL injection
At the database level, the micro-ORM component Dapper prevents SQL Injections.
Client security
HTTPS on localhost
To use the Payroll Engine from a protected network zone, the payroll console and web application use the HTTPS protocol to communicate with the backend server. During setup, a developer certificate is installed on localhost.
Web application server security
User Login
New users are initially created by the administrator or supervisor without a password. When logging in for the first time, the user must set his password and can change this later in the user settings. This results in the following login sequence: 1. Input of the user identifier 2. Selection of the tenant - if the user identifier exists in several tenants 3. Input new password with confirmation - in case of first login 4. Input password 5. Web application start
If a user is assigned to several tenants, a separate password applies to each tenant.
User authorization
The web application distinguishes between the following user types
User- user of the web application with the featuresEmployee- user of the web application and wage earner (self-employment)Administrator- has only the possibility to manage the usersSupervisor- can use all features without restriction
In the web application, the user type is indicated by the icon of the User Settings command.
Role-based access control
The functionality of the web application is controlled by Features (Feature Toggles) that are assigned to the user. Features are managed by
- users of type
Administrator - users of type
Supervisor - users with the feature
Users
The EmployeeCases feature allows access to employee cases by displaying an additional page in the navigation. Some features, such as Forecast, extend the content of existing pages.