Clarity Platform Architecture chevron_right Security & Compliance

Security & Compliance

Authentication, role-based access control, granular permissions, encryption at rest, PII handling policies, secrets management, and API security posture.

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

verified_user

Secure by Default

All endpoints require authentication via [Authorize] on the PhoenixController base class. No action is publicly accessible unless explicitly opted in.

key

JWT Configuration

AuthSettings.JwtKey and AuthSettings.JwtIssuer are injected via environment variables. Never hardcoded in source or config files.

lock_open

Explicit Public Access

[AllowAnonymous] must be applied to any endpoint intended for unauthenticated access. This is an intentional opt-in decision.

shield

PhoenixController Base

All controllers inherit from PhoenixController, which centralizes authentication, permission checks, and common controller logic.

Authentication Flow

person

Login Request

arrow_forward
fact_check

Validate Credentials

arrow_forward
token

JWT Token Issued

arrow_forward
http

Token in Header

arrow_forward
security

Authorize Middleware

arrow_forward
gpp_good

Permission Check

arrow_forward
check_circle

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

admin_panel_settings

Global Admin

Full platform access. Can manage users, roles, permissions, and all data across every schema and table.

person

User

Standard access. Permissions are determined by the granular permission system based on Schema.Table.Operation grants.

manage_accounts

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.

Broadest

*.*

Double wildcard. Matches every table in every schema. Sets the baseline permission level for the role.

Middle

Schema.*

Single wildcard. Matches all tables within a specific schema. Overrides double-wildcard settings.

Most Specific

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

input

Incoming Request

arrow_downward
verified_user

Check: User Authenticated?

arrow_downward
group

Check: Role Required?

arrow_downward
tune

Check: Permission Required?

arrow_downward
sort

Evaluate Wildcards

*.* → Schema.* → Schema.Table

arrow_downward
check_circle

Grant (200)

block

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

help

Scenario: Reporting Admin tries to CREATE a record in Invoicing.Invoice

1.

Check Invoicing.Invoice — No exact match found.

2.

Check Invoicing.* — No schema-level match found.

3.

Check *.* — Match found: Create = false.

block

Result: Denied (403). The *.* rule grants read-only access.

help

Scenario: Reporting Admin tries to CREATE a record in Reporting.SalesReport

1.

Check Reporting.SalesReport — No exact match found.

2.

Check Reporting.* — Match found: Create = true.

check_circle

Result: Granted. The Reporting.* rule provides full CRUD, overriding the *.* baseline.

help

Scenario: Product Editor tries to DELETE a record in Products.Product

1.

Check Products.Product — Exact match found: Delete = false.

block

Result: Denied (403). The exact match explicitly disallows Delete.

lightbulb

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].

lock

Default: Authorized

All controllers inherit from PhoenixController with the [Authorize] attribute. Authentication is guaranteed on every endpoint by default.

description

Swagger / OpenAPI

API documentation is auto-generated via Swagger/OpenAPI. All authenticated endpoints are documented with their security requirements and permission needs.

warning

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.

gpp_good

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.

storage

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.

key

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.

vpn_key

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.

folder_open

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.

dangerous

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.

PCI DSS SAQ-A Eligibility

Clarity's payment architecture is specifically designed to qualify for PCI DSS SAQ-A — the simplest PCI self-assessment questionnaire. This is achieved through a strict separation of card data flows from the Clarity application stack.

swap_horiz

Direct Browser-to-Provider Flow

Card data flows directly from the customer's browser to the payment provider via hosted fields or iframe. Raw card data never passes through Clarity's servers, APIs, or backend infrastructure at any point in the transaction lifecycle.

token

Tokenized Data Only

Clarity stores only the payment token, CardLastFour, BIN (for surcharge calculation), cardholder name, and expiration date. These are non-sensitive, display-safe values returned by the payment provider after tokenization.

block

Prohibited Data — Never Stored

Full PAN (Primary Account Number), CVV/CVC security codes, and full bank account numbers are never stored, processed, or transmitted by any Clarity component. This prohibition is enforced architecturally — Clarity's backend never receives this data in the first place.

verified_user

SAQ-A Qualification

Because card data never enters or passes through Clarity's infrastructure, the platform qualifies for SAQ-A — the simplest PCI self-assessment. SAQ-A applies to merchants that have fully outsourced all cardholder data functions to PCI DSS validated third-party service providers, with no electronic storage, processing, or transmission of cardholder data on the merchant's systems.

PCI Scope Boundary

shield

In PCI Scope

cloud

Payment Provider

PCI DSS Level 1 certified. Handles all card data processing, storage, and transmission.

web

Hosted Fields / iframe

Provider-hosted payment form rendered in user's browser. DOM isolated from host page.

Scope Boundary

verified_user

Out of PCI Scope

dns

Clarity Backend

Never receives or processes raw card data. Tokens only.

storage

Clarity Database

Stores only tokens, last-4, BIN, and display-safe metadata.

laptop

Clarity Frontend

Renders provider iframe. Cannot access card data in hosted fields.

hub

ERP System

Receives payment confirmation and tokens only. No card data.

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

database

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
database

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

info

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 Configuration

database

TDE Enabled at Database Level

Transparent Data Encryption is enabled at the database level for all deployments. TDE encrypts data files, transaction logs, and backups using a database encryption key. All data is encrypted on disk without requiring any application code changes — EF Core queries operate identically whether TDE is enabled or not.

lock

TLS 1.2+ for All Data in Transit

All data in transit is encrypted using TLS 1.2 or higher. This applies to all client-to-server communication, API calls, database connections, and inter-service communication. Older TLS versions (1.0, 1.1) and SSL are disabled at the infrastructure level.

security

HSTS Headers Enforced

HTTP Strict Transport Security (HSTS) headers are enforced on all responses, instructing browsers to only connect via HTTPS. This prevents protocol downgrade attacks and cookie hijacking. The Strict-Transport-Security header is set with a long max-age and includeSubDomains directive.

key

Certificate Management via Cloud Provider

TLS certificates are provisioned and managed through the cloud provider's certificate management service, ensuring automatic renewal and rotation. Certificate private keys are stored in the cloud provider's secure key store and never exposed to the application layer.

Encryption Layers

web

Application Layer

No sensitive data stored (tokenized). PII never enters this layer.

arrow_downward
enhanced_encryption

Database Layer — TDE

Data encrypted at rest on disk. Transparent to all queries and application code.

arrow_downward
hard_drive

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.

check_circle

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
block

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

1
person

Customer Entry

Customer enters payment data in the browser payment form.

2
send

Direct to Provider

Data sent directly to payment provider API. Never touches Phoenix backend.

3
token

Token Returned

Provider returns an opaque token and gateway ID back to the client.

4
save

Store Token Only

Phoenix stores only the token, last 4 digits, and display-safe fields.

5
replay

Token Reuse

Subsequent charges use the stored token via IPaymentProvider interface.

Tokenization Data Flow

laptop

Client Browser

arrow_forward
credit_card

Payment Form

arrow_forward
cloud

Payment Provider API

Nuvei, Stripe, etc.

arrow_forward
token

Token + Last4

Returned to client

arrow_forward
storage

Phoenix DB

Token, Last4, GatewayId only

verified_user

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.

Embedded Payment Forms & PCI Scope

When Clarity renders payment forms inside ERP systems such as Dynamics 365 or NetSuite, the same tokenization-first architecture applies. The payment form is isolated from the host application, preserving SAQ-A eligibility for both Clarity and the ERP host.

web

iframe / Web Component Isolation

When rendering payment forms inside an ERP, the form is isolated via an iframe or Web Component. The browser's same-origin policy prevents the host ERP page from accessing the payment form's DOM, intercepting keystrokes, or reading card data entered by the user.

block

Host Cannot Access Card Data

The host ERP page cannot access the payment form's DOM or intercept card data. The iframe boundary enforces complete isolation — the ERP application receives only the tokenized result after the customer completes the payment form submission.

send

Direct Submission to Provider

The payment form submits directly to the payment provider from within the iframe. Card data never passes through Clarity's servers or the ERP host's servers. This maintains SAQ-A eligibility for both Clarity and the ERP host application.

link

Full Details

For a comprehensive comparison of embedded vs. integrated payment form approaches, including architecture diagrams and security trade-offs, see Embedded vs. Integrated Payment Forms.

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.

hard_drive

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.

lock

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_file

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.

share

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

check_circle

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.

check_circle

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.

check_circle

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.

block

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.

help

Frequently Asked Questions