Security & Compliance
Authentication, role-based access control, granular permissions, encryption at rest, PII handling policies, secrets management, and API security posture.
On this page
login Authentication Model
The Clarity platform uses ASP.NET Core Identity combined with JWT bearer tokens for all authentication. Every API endpoint requires authentication by default through the PhoenixController base class, which applies the [Authorize] attribute globally.
Key Principles
Secure by Default
All endpoints require authentication via [Authorize] on the PhoenixController base class. No action is publicly accessible unless explicitly opted in.
JWT Configuration
AuthSettings.JwtKey and AuthSettings.JwtIssuer are injected via environment variables. Never hardcoded in source or config files.
Explicit Public Access
[AllowAnonymous] must be applied to any endpoint intended for unauthenticated access. This is an intentional opt-in decision.
PhoenixController Base
All controllers inherit from PhoenixController, which centralizes authentication, permission checks, and common controller logic.
Authentication Flow
Login Request
Validate Credentials
JWT Token Issued
Token in Header
Authorize Middleware
Permission Check
Access Granted
PhoenixController Base Class
// Core/Phoenix.Core/Controllers/PhoenixController.cs [Authorize] // All endpoints require login by default [ApiController] public abstract class PhoenixController : ControllerBase { // Common services: DbContext, ICurrentUser, IPermissionService // All inheriting controllers automatically require authentication } // Public endpoint example — explicit opt-in [AllowAnonymous] // Explicitly marks endpoint as public [HttpGet("api/geography/countries")] public async Task<IActionResult> GetCountries() { // Only for freely available, non-sensitive data return Ok(await _countryService.GetAllAsync()); }
group Role-Based Access Control
Roles are assigned to users and serve as high-level authorization labels. The platform uses ASP.NET Core's built-in [Authorize(Roles = "...")] attribute to restrict endpoints to specific user types. Roles work in conjunction with the granular permission system for fine-grained access control.
Role Assignment
Global Admin
Full platform access. Can manage users, roles, permissions, and all data across every schema and table.
User
Standard access. Permissions are determined by the granular permission system based on Schema.Table.Operation grants.
Custom Roles
Client-defined roles with specific permission sets. Each role can be assigned granular CRUD permissions per schema and table.
Role-Based Authorization
// Restrict endpoint to Global Admin role only [Authorize(Roles = "Global Admin")] [HttpPost("api/admin/users")] public async Task<IActionResult> CreateUser(CreateUserDto dto) { // Only Global Admins can create new users return Ok(await _userService.CreateAsync(dto)); } // Multiple roles allowed [Authorize(Roles = "Global Admin,Reporting Admin")] [HttpGet("api/reports/dashboard")] public async Task<IActionResult> GetDashboard() { return Ok(await _reportService.GetDashboardAsync()); }
tune Permission System
The permission system provides granular, table-level access control using a wildcard-based model. Each permission record maps a Role to a Schema and Table, with boolean flags for Create, Read, Update, and Delete operations.
Permission Entity
// Core/Phoenix.Core/Models/Permission.cs public class Permission : BaseEntity { public int RoleId { get; set; } public string Schema { get; set; } // "*" = all schemas public string Table { get; set; } // "*" = all tables in schema public bool Create { get; set; } public bool Read { get; set; } public bool Update { get; set; } public bool Delete { get; set; } }
Wildcard Operators & Specificity
Permission records are evaluated by specificity. More specific records override broader wildcard matches. This allows a single wildcard rule to set baseline access, with targeted overrides for individual schemas or tables.
*.*
Double wildcard. Matches every table in every schema. Sets the baseline permission level for the role.
Schema.*
Single wildcard. Matches all tables within a specific schema. Overrides double-wildcard settings.
Schema.Table
Zero wildcards. Targets an exact schema and table combination. Always wins over wildcard matches.
PermissionAuthorize Attribute
// Usage on controller actions [PermissionAuthorize("Products.Product.C")] // Require Create on Products.Product [HttpPost("api/products")] public async Task<IActionResult> CreateProduct(ProductDto dto) { return Ok(await _productService.CreateAsync(dto)); } [PermissionAuthorize("Reporting.SalesReport.R")] // Require Read on Reporting.SalesReport [HttpGet("api/reports/sales")] public async Task<IActionResult> GetSalesReport() { return Ok(await _reportService.GetSalesAsync()); } // Format: "Schema.Table.Operation" // Operations: C = Create, R = Read, U = Update, D = Delete
Permission Configuration Examples
| Role | Schema | Table | C | R | U | D | Effect |
|---|---|---|---|---|---|---|---|
| Global Admin | * | * | T | T | T | T | Full access to everything |
| Read-Only User | * | * | F | T | F | F | Read any table, no writes |
| Reporting Admin | * | * | F | T | F | F | Read anything... |
| Reporting Admin | Reporting | * | T | T | T | T | ...plus full Reporting access |
| Product Editor | Products | Product | T | T | T | F | CRUD Products, no deletes |
Permission Evaluation Flow
Incoming Request
Check: User Authenticated?
Check: Role Required?
Check: Permission Required?
Evaluate Wildcards
*.* → Schema.* → Schema.Table
Grant (200)
Deny (403)
PermissionAuthorizationHandler
The PermissionAuthorizationHandler is the core authorization logic that evaluates permission records at runtime. It retrieves the user's role, looks up all matching permission records, sorts by specificity, and applies the most specific match.
// Simplified permission evaluation pseudocode var permissions = GetPermissionsForRole(user.RoleId); // Sort by specificity (most specific first) var sorted = permissions .OrderByDescending(p => GetSpecificity(p)) .ToList(); // Specificity scoring: // Schema=*, Table=* → 0 (broadest) // Schema=X, Table=* → 1 // Schema=X, Table=Y → 2 (most specific, wins) var match = sorted.FirstOrDefault(p => MatchesSchema(p.Schema, requestedSchema) && MatchesTable(p.Table, requestedTable)); if (match == null || !match.HasOperation(requestedOp)) return Forbid(); // 403
psychology Permission Examples
The following walkthrough demonstrates how wildcard resolution and specificity work in practice, using a Reporting Admin role as an example.
Wildcard Resolution Walkthrough
Scenario: Reporting Admin tries to CREATE a record in Invoicing.Invoice
Check Invoicing.Invoice — No exact match found.
Check Invoicing.* — No schema-level match found.
Check *.* — Match found: Create = false.
Result: Denied (403). The *.* rule grants read-only access.
Scenario: Reporting Admin tries to CREATE a record in Reporting.SalesReport
Check Reporting.SalesReport — No exact match found.
Check Reporting.* — Match found: Create = true.
Result: Granted. The Reporting.* rule provides full CRUD, overriding the *.* baseline.
Scenario: Product Editor tries to DELETE a record in Products.Product
Check Products.Product — Exact match found: Delete = false.
Result: Denied (403). The exact match explicitly disallows Delete.
Specificity Rule
The most specific matching permission always wins. Schema.Table overrides Schema.*, which overrides *.*. This allows administrators to set broad baseline permissions and apply targeted restrictions or grants as needed.
api API Security
Every API endpoint is secured by default. The platform follows a deny-by-default posture where authentication is required on all routes unless explicitly opted out with [AllowAnonymous].
Default: Authorized
All controllers inherit from PhoenixController with the [Authorize] attribute. Authentication is guaranteed on every endpoint by default.
Swagger / OpenAPI
API documentation is auto-generated via Swagger/OpenAPI. All authenticated endpoints are documented with their security requirements and permission needs.
IModel<T> Warning
The IModel<T> interface with [AllowAnonymous] should only be used for freely public data such as geography lookups, type enumerations, and status codes.
Permission Layer
Beyond authentication, endpoints can require specific CRUD permissions via [PermissionAuthorize] for granular, table-level access control.
database Data Handling
All data persistence is managed through Entity Framework Core with strict separation of credentials from application code. Connection strings, signing keys, and API credentials are injected at runtime via Kubernetes secrets and environment variables.
SQL Database via Entity Framework Core
Supports both PostgreSQL and SQL Server. The database provider is auto-detected from the connection string format at startup. All queries go through EF Core's parameterized query system, preventing SQL injection.
Connection String from Kubernetes Secret
The database connection string is stored in the db.connectionString Kubernetes secret and injected as an environment variable. It is never present in configuration files or source control.
JWT Configuration via Environment Variables
JwtKey and JwtIssuer are injected as environment variables from Kubernetes secrets. These sensitive values are never hardcoded in appsettings.json or committed to the repository.
File Storage: NFS Persistent Volume
File storage uses an NFS PVC mounted at /usr/share/phx with ReadWriteMany access mode. File access is controlled at the application level through authorization and validated via ValidateFileUploadPipeline hooks.
Never Hardcode Credentials
No credentials, connection strings, API keys, or signing secrets are committed to source control or stored in configuration files. All sensitive values are injected at runtime via Kubernetes secrets.
enhanced_encryption Encryption at Rest / TDE
Transparent Data Encryption (TDE) is enabled for all database deployments. TDE encrypts data files, log files, and backups at the storage level, making the encryption completely transparent to the application. EF Core queries operate identically with or without TDE enabled.
Encryption by Database Provider
SQL Server TDE
- check_circle Built-in feature, enabled per database
- check_circle Encrypts data files, log files, and backups
- check_circle No application code changes required
- check_circle Certificate-based key management
PostgreSQL Encryption
- check_circle pgcrypto extension for column-level encryption
- check_circle Disk-level encryption via OS or cloud provider
- check_circle Transparent to EF Core queries
- check_circle Cloud-managed key rotation available
How TDE Works
Data is encrypted on disk and decrypted in memory during query execution. The encryption and decryption process is handled entirely by the database engine, requiring no changes to application code or EF Core queries.
Encryption Layers
Application Layer
No sensitive data stored (tokenized). PII never enters this layer.
Database Layer — TDE
Data encrypted at rest on disk. Transparent to all queries and application code.
Storage Layer
Optional disk/volume encryption. Additional layer via cloud provider or OS-level encryption.
policy No PII Storage Policy
Core Principle: All personally identifiable and sensitive payment information is tokenized before entering any aspect of the data persistence layer. The Phoenix platform never stores, processes, or transmits raw payment card data.
What IS Stored
- check Gateway tokens (opaque, provider-issued)
- check Last 4 digits of card or account number
- check BIN (first 6-8 digits) for surcharge calculation
- check Cardholder name
- check Expiration date
- check Gateway ID and transaction references
What is NEVER Stored
- close Full card numbers (PAN)
- close Full bank account numbers
- close CVV / CVC security codes
- close Social Security numbers
- close Raw authentication credentials
- close Any unmasked PII
Tokenization Flow
Customer Entry
Customer enters payment data in the browser payment form.
Direct to Provider
Data sent directly to payment provider API. Never touches Phoenix backend.
Token Returned
Provider returns an opaque token and gateway ID back to the client.
Store Token Only
Phoenix stores only the token, last 4 digits, and display-safe fields.
Token Reuse
Subsequent charges use the stored token via IPaymentProvider interface.
Tokenization Data Flow
Client Browser
Payment Form
Payment Provider API
Nuvei, Stripe, etc.
Token + Last4
Returned to client
Phoenix DB
Token, Last4, GatewayId only
PCI Compliance Approach
By never handling, transmitting, or storing raw card data, the platform minimizes its PCI DSS scope. All sensitive cardholder data is processed exclusively by PCI-certified payment providers. Phoenix operates as a SAQ-A eligible merchant integration, delegating all card data handling to the provider's certified infrastructure.
folder_special File Storage Security
File storage is managed through an NFS Persistent Volume Claim mounted into the application container. All file access is controlled at the application level through authentication and authorization, with upload validation enforced by pipeline hooks.
NFS PVC Mount
Files are stored on an NFS persistent volume mounted at /usr/share/phx. The volume uses ReadWriteMany access mode, allowing the application to read and write files across replicas.
Application-Level Access Control
All file endpoints require authentication. File access is controlled by the same authorization and permission system used for API endpoints. No anonymous file access is permitted.
Upload Validation
File uploads are validated through ValidateFileUploadPipeline hooks. These hooks enforce file type restrictions, size limits, and content validation before files are persisted to storage.
Shared Storage
The ReadWriteMany access mode ensures all pod replicas share the same file storage. Uploaded files are immediately available across all instances without synchronization delays.
vpn_key Secrets Management
All sensitive configuration values are managed through Kubernetes secrets and injected as environment variables at runtime. No credentials are hardcoded in source code, configuration files, or container images.
Managed Secrets
| Category | Secret | Injection Method | Purpose |
|---|---|---|---|
| Database | db.connectionString | Kubernetes Secret → Env Var | EF Core database connection |
| Authentication | AuthSettings.JwtKey | Kubernetes Secret → Env Var | JWT token signing key |
| Authentication | AuthSettings.JwtIssuer | Kubernetes Secret → Env Var | JWT token issuer identifier |
| Connectors | OAuth Client Secrets | Settings (encrypted) | ERP connector OAuth credentials |
| Connectors | TBA Tokens | Settings (encrypted) | NetSuite Token-Based Auth |
| Payments | Provider API Keys | Settings (encrypted) | Payment gateway credentials |
Security Rules
Environment Variable Injection
All secrets are injected as environment variables at container startup. The application reads them from the environment, never from files on disk or hardcoded values.
Never Committed to Source Control
No credentials, tokens, or signing keys are present in the repository. Configuration files contain only non-sensitive defaults and structure.
Connector Credentials in Settings
OAuth client secrets and TBA tokens for ERP connectors are stored in the application Settings system (not the database). These values are managed through the admin UI and encrypted in transit.
Never Hardcoded
Hardcoding credentials in source code, appsettings.json, Dockerfiles, or Kubernetes manifests is strictly prohibited. All sensitive values must be managed through the secrets pipeline.