Skip to content

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 key
  • OAuth: 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 URL
  • Audience — the expected audience claim
  • RequireHttpsMetadata — enforce HTTPS for metadata discovery
  • ClientSecret — 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 access
  • System.Net — network access
  • System.Diagnostics — process execution
  • System.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 70007030 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
Clients can read this header to verify compatibility or for diagnostic purposes. The version follows Semantic Versioning (major.minor).

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 features
  • Employee - user of the web application and wage earner (self-employment)
  • Administrator - has only the possibility to manage the users
  • Supervisor - 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.