Overview

Prima server configuration is managed through the appsettings.json file located in the Prima installation directory (typically C:\Program Files\Fortelinea Software Systems\Prima\Services\PrimaWebService).

Configuration File Structure

The configuration uses the standard ASP.NET Core configuration system. Settings can be overridden using:

  • appsettings.json - Base configuration
  • appsettings.{Environment}.json - Optional overrides for the environment named by ASPNETCORE_ENVIRONMENT (Production when unset)
  • Environment variables
  • Command-line arguments
The installer rewrites some sections. Every install or upgrade with the Prima Server Installer rewrites several sections of appsettings.json from the installer's own saved settings, including ConnectionStrings, Kestrel, LdapConfiguration, OktaConfiguration, OidcConfiguration, ImageStorageDirectories, and Logging. Change those settings in the installer (or make the same change there too), or your hand edits are lost at the next upgrade. Sections the installer does not manage, such as ImageStorage and LabelTemplateStorage, are kept as you set them.

Configuration Sections

Use the tabs on the left to learn about configuring each section:

  • Authentication - Windows/LDAP authentication, single sign-on (Okta, Microsoft Entra ID), JWT and token certificate settings, Identity configuration
  • Image Storage - Local file storage, S3-compatible cloud storage, path configuration
  • Web Server (Kestrel) - URLs, ports, HTTPS/SSL certificate, request size limit, load balancers
  • Database Connection - The SQL Server connection string
Tip: After making configuration changes, restart the Prima Windows service for changes to take effect.

Authentication

Prima supports four authentication methods. The method in use is decided by LdapConfiguration:AuthenticationType together with which configuration sections are fully configured (see Authentication Priority).

Supported Authentication Methods

Method Use Case Configuration Section
Prima Built-in database authentication (default) No additional configuration required
LDAP Active Directory / LDAP integration LdapConfiguration
Single sign-on (OpenID Connect) Users sign in through your organization's identity provider (Okta, Microsoft Entra ID, or another OpenID Connect provider) in the browser. Prima passwords are not used. See Single Sign-On. OidcConfiguration, with LdapConfiguration:AuthenticationType set to "Oidc"
Okta (password) Users type their Okta password into Prima's own sign-in screen; Prima checks it with Okta behind the scenes OktaConfiguration

Authentication Priority

When multiple authentication methods are configured, Prima uses this priority order:

  1. Single sign-on - If LdapConfiguration has AuthenticationType: "Oidc" and OidcConfiguration is fully configured
  2. Okta (password) - If OktaConfiguration is fully configured
  3. LDAP - If LdapConfiguration has AuthenticationType: "Ldap" and names an LDAP server (Host set to anything other than localhost; see LDAP)
  4. Prima - Default fallback (built-in database authentication)

Single sign-on applies only when AuthenticationType selects it, so setting AuthenticationType back to another value always turns it off — useful if your identity provider is ever unavailable.

Prima Server Installer: the installer's Authentication page sets all of this for you. Choose OpenID Connect – Authorization Code (single sign-on) for single sign-on, or Okta – Resource Owner Password for the older Okta password mode, then Apply Settings. You can also edit appsettings.json by hand as described below, but every install or upgrade rewrites LdapConfiguration from the installer's saved settings, and resets OktaConfiguration and OidcConfiguration to placeholders unless the installer's own mode uses them. Make the same change on the installer's Authentication page, or it is lost at the next upgrade.

JWT Token Configuration

Prima issues tokens to the desktop applications and API integrations regardless of which authentication method is active. JwtTokenSettings sets how long they last. The tokens themselves are signed with the token certificates.

{
  "JwtTokenSettings": {
    "Secret": "your-secret-key-here-minimum-32-characters",
    "AccessTokenTTL": 240,
    "RefreshTokenTTL": 7,
    "DesktopApplicationAutoLogout": 120
  }
}
Property Type Description
Secret string Signs the internal tokens Prima's own services present to each other when they share one-at-a-time locks (case numbers, instruments, containers). This only matters across servers when another service takes its locks from this one: SRM using the web service's lock endpoint (LockService:ListenAddress), or a split deployment where every server uses LockService:Provider "Remote". Each of those services must have the same value. The installer generates a random value when it is empty or "REPLACEME", and the web service will not start if it is blank.
AccessTokenTTL integer Access token time-to-live in minutes (default: 240)
RefreshTokenTTL integer Refresh token time-to-live in days (default: 7). Unused tokens older than this are also removed from the database.
DesktopApplicationAutoLogout integer Minutes of inactivity after which the desktop applications (Workstation, Control Panel, Lab Manager, Pathologist) sign the user out (default: 120)
Security: If you set Secret by hand, use a strong, random string of at least 32 characters. The installer replaces an empty or "REPLACEME" value with a generated one (and resets AccessTokenTTL and RefreshTokenTTL to their defaults when it does).

Token Certificates

The web service's token server uses two certificates, configured in the TokenServer section. The signing certificate signs every token it issues. The encryption certificate encrypts the tokens only the server itself reads back, such as authorization codes and refresh tokens. Access tokens are signed but not encrypted, so API integrations can read them. The Prima Server Installer creates them for you: two self-signed certificates, Prima Token Signing and Prima Token Encryption, in the LocalMachine\My certificate store, with read access for the web service's account. It writes their thumbprints to appsettings.json and keeps them on every upgrade.

{
  "TokenServer": {
    "Issuer": "https://prima.company.com",
    "SigningCertificateThumbprint": "3F2A...",
    "EncryptionCertificateThumbprint": "9B71..."
  }
}
Property Type Description
Issuer string The public HTTPS address clients reach Prima on; tokens carry it as their issuer. The installer sets it to https://{server}:{https port} when it is empty, or when it still equals the address the installer derived last time (so it follows a change of server name). Any other value, such as a load balancer's public address, is kept. Changing the issuer invalidates every token already issued.
SigningCertificateThumbprint / EncryptionCertificateThumbprint string Certificates in the LocalMachine\My store. Written by the installer.
SigningCertificatePath / EncryptionCertificatePath string Alternatively, PFX files to load the certificates from. When either is set, the installer leaves the certificates as configured.
SigningCertificatePassword / EncryptionCertificatePassword string Passwords of the PFX files (or of the base64 certificates).
SigningCertificateBase64 / EncryptionCertificateBase64 string Alternatively, the PFX files' contents encoded as base64, for hosts that can only supply secrets as environment variables.
CertificateStoreLocation string The store the thumbprints are looked up in (its My store): "LocalMachine" (default) or "CurrentUser".
TokenRequestsPerMinutePerAddress integer How many token requests (/connect/token) one client address may make per minute; more are refused with HTTP 429 (default: 120). Raise it if many desktop users reach Prima through one address, for example behind a proxy.
DisableTransportSecurityRequirement boolean Accept token server requests over plain HTTP (default: false). Only for a server behind a proxy that ends TLS and does not forward the original scheme; leave it off on a normal Windows install.

Each certificate must come from exactly one source: a thumbprint, a PFX path, or base64.

Keep the certificates. Replacing them signs everyone out and invalidates every refresh token integrations hold. The installer only creates new ones when the configured certificate is missing, has lost its private key, or has expired (they are valid for 10 years), and warns when it does.
More than one web server: every server that issues or accepts Prima tokens must use the same two certificates. The installer's certificates cannot be copied to another machine (their private keys are not exportable), so supply your own PFX files on each server through SigningCertificatePath / EncryptionCertificatePath and their passwords. Clear the thumbprints the installer wrote (set SigningCertificateThumbprint and EncryptionCertificateThumbprint to ""): with both set the web service will not start, and logs Set only one of TokenServer:SigningCertificateThumbprint, TokenServer:SigningCertificatePath or TokenServer:SigningCertificateBase64 for the token signing certificate. Once a path is set, the installer leaves the certificates alone on upgrades.

These are not HTTPS certificates; the web server's HTTPS certificate is configured separately. A token certificate that is missing, has the wrong password, or cannot do its job stops the web service at startup with a log message naming the TokenServer setting to fix.


LDAP / Active Directory Configuration

Configure LDAP authentication to integrate with your organization's Active Directory.

{
  "LdapConfiguration": {
    "AuthenticationType": "Ldap",
    "Host": "ad.company.com",
    "Port": 636,
    "Domain": "company.com",
    "UserStore": "OU=Employees,DC=company,DC=com",
    "Protocol": "LDAPS",
    "AuthType": "Negotiate",
    "LdapVersion": 3,
    "TimeOut": "00:01:00",
    "SslVerificationMethod": "Default"
  }
}
Required Properties
Property Type Description
AuthenticationType string Must be set to "Ldap" to enable LDAP authentication
Host string LDAP server hostname or IP address (e.g., "ad.company.com"). LDAP is only used when this names a server other than localhost; the shipped file contains "localhost", so replace it (or remove the key to use auto-discovery, below).
Optional Properties
Property Type Default Description
Protocol string "LDAPS" "LDAP" (port 389, unencrypted) or "LDAPS" (port 636, encrypted)
Port integer 636 LDAP server port. 636 for LDAPS, 389 for LDAP.
Domain string Auto-detected Active Directory domain name (e.g., "company.com")
UserStore string - Distinguished name for user search (e.g., "OU=Users,DC=company,DC=com")
LdapVersion integer 3 LDAP protocol version (2 or 3)
AuthType string "Negotiate" Authentication method: "Basic", "Negotiate", or "Digest"
TimeOut timespan "01:00:00" LDAP connection timeout
LocatorFlags string "OnlyLdapNeeded, DirectoryServicesPreferred" Domain controller locator flags (.NET LocatorFlags names, comma-separated). The shipped file uses "DirectoryServicesPreferred".
SslVerificationMethod string "Default" How the LDAPS server's certificate is checked: "Default" (Windows' normal certificate checks), "LocalFile" (compare it with a certificate file exported from the server; set SslVerificationCertificatePath), or "AlwaysAccept" (accept any certificate; for a self-signed certificate or an untrusted root CA).
SslVerificationCertificatePath string - Path to the server's certificate file, when SslVerificationMethod is "LocalFile"
Auto-Discovery: When the Host key is left out entirely, Prima uses the primary domain controller of the domain the server belongs to (and that domain as Domain). This needs a domain-joined server. An empty Host or "localhost" does not trigger discovery: LDAP is then treated as not configured and Prima falls back to built-in authentication.

Single Sign-On (Okta, Microsoft Entra ID, OpenID Connect)

With single sign-on, everyone signs in through your organization's identity provider in the browser: the Prima website directly, and the desktop applications by opening a browser window. Prima passwords are not used, and password sign-ins are refused. Because users sign in on the provider's own page, the provider's sign-in policies, including multi-factor authentication, apply. Okta, Microsoft Entra ID (formerly Azure AD), and other OpenID Connect providers are supported.

Okta sites: this is the recommended way to use Okta. The older Okta password mode has users type their Okta password into Prima and cannot complete MFA.
Step 1: Register Prima with your identity provider

Only the Prima web service is registered; the desktop applications sign in through it, so there is nothing to register per workstation. You need these two addresses, on the HTTPS address users reach Prima on (the installer's single sign-on page shows both):

Sign-in redirect address (required) https://prima.company.com/signin-oidc
Sign-out redirect address (optional, if the provider asks) https://prima.company.com/signout-callback-oidc
Register the address exactly as users reach Prima. Prima builds the redirect address from the address in the user's browser, and providers require an exact match. On the standard HTTPS port 443 browsers leave the port out, so register https://prima.company.com/signin-oidc. The installer shows the address with the port (for example https://prima.company.com:443/signin-oidc); if your provider accepts several redirect addresses, register that form as well. On any other port, include it.

Microsoft Entra ID (Entra admin center):

  1. App registrations > New registration. Supported account types: Accounts in this organizational directory only (single tenant). Prima signs in the users of one directory only, and refuses the multi-tenant common / organizations addresses.
  2. Redirect URI: platform Web, the sign-in redirect address above.
  3. From Overview, note the Application (client) ID and the Directory (tenant) ID.
  4. Certificates & secrets > New client secret. Copy its Value (not the Secret ID). Note the expiry date: when the secret expires, nobody can sign in until it is renewed (see Renewing the client secret).
  5. If your tenant does not let users consent to applications themselves, open API permissions and Grant admin consent for openid, profile, and email (Microsoft Graph, delegated).
  6. Optional: to limit who can sign in, open Enterprise applications > the Prima application > Properties, set Assignment required to Yes, and assign users or groups. Everyone also needs a Prima account (see Step 3).

Okta (Admin Console):

  1. Applications > Create App Integration > OIDC - OpenID Connect > Web Application.
  2. Grant type: Authorization Code (the default). Sign-in redirect URIs: the sign-in redirect address above. Sign-out redirect URIs: the sign-out address above.
  3. Assignments: assign the people or groups who use Prima. Okta refuses anyone not assigned.
  4. Copy the Client ID and Client secret.
  5. Prima uses Okta's default custom authorization server. If your Okta org does not have custom authorization servers, set OktaAuthorizationServerId to an empty value ("") to use the org authorization server instead.

Other OpenID Connect providers: register a confidential web application with the authorization code flow and the sign-in redirect address above, and set Provider to "Generic" with Authority set to the provider's issuer URL (it must be https://).

Step 2: Configure the web service

In the Prima Server Installer, open the Authentication page, choose OpenID Connect – Authorization Code (single sign-on), fill in the identity provider, client ID, and client secret, and use the connection test to check that Prima can reach the provider and that the provider accepts the client ID and secret. Then Apply Settings. The installer writes these sections of appsettings.json. You can also edit them by hand, but every install or upgrade rewrites them from the installer's saved settings, so make the same change in the installer:

{
  "LdapConfiguration": {
    "AuthenticationType": "Oidc"
  },
  "OidcConfiguration": {
    "Provider": "Entra",
    "DisplayName": "Sign in with Microsoft",
    "EntraTenantId": "00000000-0000-0000-0000-000000000000",
    "ClientId": "00000000-0000-0000-0000-000000000000",
    "ClientSecret": "your-client-secret",
    "UserMatchClaims": "preferred_username email",
    "UserMatchIgnoreDomain": false
  }
}
Property Default Description
LdapConfiguration:AuthenticationType — Must be "Oidc" to turn single sign-on on.
Provider "Generic" "Entra", "Okta", or "Generic". Decides how the provider's address is built.
DisplayName "Sign in with your organization's account" The text on the sign-in button.
EntraTenantId — Entra only: the Directory (tenant) ID, or a verified domain. common, organizations, and consumers are refused.
EntraCloud "Public" Entra only: "Public", "USGovernment", or "China".
OktaDomain — Okta only: your Okta org domain, e.g. "company.okta.com".
OktaAuthorizationServerId "default" Okta only: the custom authorization server's id, or "" for the org authorization server.
Authority — Generic only: the provider's issuer URL, as its discovery document gives it. Must be https://.
ClientId / ClientSecret — From Step 1.
Scopes "openid profile email" Space-separated; must include openid.
UserMatchClaims "preferred_username email" Claims compared with Prima user names on a user's first sign-in, in order. See Step 3.
UserMatchIgnoreDomain false true when Prima user names have no domain, so that jsmith@company.com also matches jsmith.

Restart the Prima web service. If a setting is missing, it logs the warning LdapConfiguration:AuthenticationType is Oidc, but sign-in uses Prima because OidcConfiguration is incomplete: followed by what to fix, and keeps password sign-in (the message says Okta instead of Prima if OktaConfiguration is also filled in). No warning means single sign-on is on; confirm it with Test sign-in (Step 4).

Step 3: How users are matched to Prima accounts

Single sign-on does not create Prima accounts: each person still needs a Prima user. The first time someone signs in, Prima compares the provider's claims, in the order of UserMatchClaims, with Prima user names (ignoring case). The first claim that matches decides, and it must match exactly one active user. From then on the two are linked by the provider's permanent id for that person, so a later change of name or e-mail at the provider changes nothing. An administrator can remove the link from the user in the Control Panel (Unlink).

  • Entra: preferred_username is the user principal name, e.g. jsmith@company.com.
  • Okta: preferred_username is the Okta username, usually the e-mail address.
  • If your Prima user names are plain names such as jsmith (common for sites that used LDAP), set UserMatchIgnoreDomain to true (Ignore the domain when matching in the installer).
The first administrator. On a new installation, the administrator user name entered on the installer's Database Init page must be the administrator's sign-in name at the provider (e.g. jsmith@company.com), or the part before the @ with UserMatchIgnoreDomain turned on. Otherwise nobody can sign in to set up everyone else.
Step 4: Check it works
  • Test sign-in in the installer, on the Authentication page once the services are installed, or on the Install page after an install or upgrade, signs you in through your browser the way the desktop applications do and shows which Prima user you are and your roles.
  • Browse to Prima. The sign-in page shows a single button with your DisplayName, and no user name and password boxes.
  • Start a desktop application. Signing in opens a browser window, which goes through your provider (or straight through if you are already signed in there), then returns to the application.
Shared workstations: the desktop applications offer to sign the last user in automatically. On a workstation several people use, mark it Shared in the Control Panel so Prima always asks who is signing in.
Troubleshooting single sign-on
The sign-in page still asks for a password
Single sign-on is not on. Check the web service's startup log line above; it names the missing setting.
The provider says the redirect URI does not match
The address registered at the provider must be exactly https://<the Prima address>/signin-oidc. Prima builds it from the address the browser used, so users must reach Prima on that address, over HTTPS.
"Correlation failed" after signing in at the provider
The browser did not return the sign-in cookies, usually because the provider sent the user back to a different hostname than the one they started on. Use one address everywhere: in the redirect URI, in bookmarks, and in the desktop applications' server address.
"Your account is not set up in Prima"
No single active Prima user matched. The web service's log says why, with the claims and values it tried (search it for Single sign-on refused). Create the user with that user name, or adjust UserMatchClaims / UserMatchIgnoreDomain.
"Your Prima account is not active"
The matching user is deactivated in Prima.
Renewing the client secret

Entra client secrets expire (at most two years after they are created). Before that date, create a new secret at the provider, enter it on the installer's Authentication page (or in OidcConfiguration:ClientSecret), and restart the Prima web service.

Turning single sign-on off

Set LdapConfiguration:AuthenticationType back to "Prima" (or another method) and restart the web service. Password sign-in returns; users who only ever signed in through the provider have no Prima password and need one set.


Okta Configuration (Password Mode)

Configure Okta for cloud-based identity management using OAuth 2.0. In this mode users type their Okta password into Prima's own sign-in screen. For sign-in through Okta's own page, with MFA, use single sign-on instead.

{
  "OktaConfiguration": {
    "Domain": "mycompany.okta.com",
    "ClientId": "0oab1234567890ABC",
    "ClientSecret": "X7aB_cD-eFg_HiJk_lMnOpQrStUvWxYz123456789",
    "AuthorizationServerId": "default"
  }
}
Required Properties
Property Type Description
Domain string Your Okta org domain, as a bare host name without https:// (e.g., "company.okta.com")
ClientId string OAuth 2.0 application client ID from Okta
ClientSecret string OAuth 2.0 application client secret from Okta
Optional Properties
Property Type Default Description
AuthorizationServerId string "default" Okta custom authorization server ID. Must not be empty in this mode (the org authorization server is not supported here).

TokenValidationCacheDuration, ValidateAudience, and ValidateIssuer may appear in older files; Prima does not use them in this mode, and they can be removed.

Setting Up the Okta Application (Okta Admin Console)

These steps are performed in the Okta Admin Console by your Okta administrator. Prima validates user credentials directly against Okta's token endpoint using the OAuth 2.0 Resource Owner Password grant — users sign in on Prima's own login screen, and Prima verifies the password with Okta behind the scenes. No browser redirect to Okta is involved.

  1. In the Okta Admin Console, go to Applications > Applications and click Create App Integration.
  2. Select OIDC - OpenID Connect as the sign-in method.
  3. Select Web Application as the application type. Prima authenticates as a confidential client and needs both a client ID and a client secret, which this type provides.
  4. Give the application a name (for example, Prima).
  5. Leave the Sign-in redirect URIs field at its default value (e.g. http://localhost/callback). Because Prima never redirects the user's browser to Okta, redirect URIs are not used — Okta simply requires a value in this field. Do not check the Allow wildcard * in sign-in URI redirect box.
  6. Under Assignments, choose which users or groups may use Prima. Users who are not assigned to the application will fail to authenticate.
  7. Save the application, then open its General tab and click Edit in the General Settings section.
  8. Under Grant type, check Resource Owner Password (it may be listed under Advanced). This step is required — without it, every Prima login will fail even when the credentials are correct.
  9. Copy the Client ID and Client Secret from the Client Credentials section and enter them, along with your Okta domain, in the OktaConfiguration section of appsettings.json as shown above.
User accounts: Okta replaces password verification only — each user must still exist as a Prima user, and the Prima username must match the user's Okta sign-in name (typically their email address). Prima sends the Prima username to Okta during login, so a mismatch will cause authentication to fail.
Authorization server: Prima authenticates against the Okta authorization server named by AuthorizationServerId ("default" unless configured otherwise). The default custom authorization server is included with Okta's API Access Management feature; if your Okta org does not have it, set AuthorizationServerId to an authorization server that exists in your org. The openid, profile, and email scopes Prima requests are present on the default server out of the box.
Multi-factor authentication: The Resource Owner Password grant cannot complete MFA challenges. Users covered by an Okta sign-on policy that requires MFA will not be able to log in to Prima. Your Okta administrator may need to configure an authentication policy exception for the Prima application, or switch Prima to single sign-on, where users sign in on Okta's own page and MFA works.
Token endpoint: Prima checks each password by calling https://{Domain}/oauth2/{AuthorizationServerId}/v1/token
Example: https://company.okta.com/oauth2/default/v1/token. The web server must be able to reach it.

Configure session cookies for web UI authentication.

{
  "CookieAuthentication": {
    "ExpireTimeSpan": "01:30:00",
    "SlidingExpiration": true
  },
  "CookiePolicyOptions": {
    "Secure": "Always",
    "HttpOnly": "Always",
    "MinimumSameSitePolicy": "Lax"
  }
}
Property Type Default Description
ExpireTimeSpan timespan "01:30:00" Session timeout duration
SlidingExpiration boolean true Extend expiration on each request
Secure string "Always" Cookie transmission: "Always", "None", "SameAsRequest"
HttpOnly string "Always" Prevent JavaScript access: "Always" or "None"
MinimumSameSitePolicy string "Lax" CSRF protection: "None", "Lax", "Strict"
Load Balancer Note: Keep "Secure": "Always" behind a load balancer that terminates HTTPS: the browser still talks HTTPS to the load balancer. What Prima needs instead is to see the original HTTPS address, so turn on forwarded headers (see Load Balancers and Proxies on the Web Server (Kestrel) tab). Without them, single sign-on builds its redirect address as http:// and the provider rejects it.

Configuration Examples

Example 1: LDAP with Active Directory
{
  "LdapConfiguration": {
    "AuthenticationType": "Ldap",
    "Host": "ad.company.com",
    "Port": 636,
    "Domain": "company.com",
    "UserStore": "OU=Employees,DC=company,DC=com",
    "Protocol": "LDAPS",
    "AuthType": "Negotiate",
    "LdapVersion": 3,
    "TimeOut": "00:01:00"
  },
  "JwtTokenSettings": {
    "Secret": "your-production-secret-key-at-least-32-chars",
    "RefreshTokenTTL": 7
  }
}
Example 2: Okta Password Mode
{
  "OktaConfiguration": {
    "Domain": "mycompany.okta.com",
    "ClientId": "0oab1234567890ABC",
    "ClientSecret": "X7aB_cD-eFg_HiJk_lMnOpQrStUvWxYz123456789",
    "AuthorizationServerId": "default"
  },
  "JwtTokenSettings": {
    "Secret": "your-production-secret-key-at-least-32-chars",
    "RefreshTokenTTL": 7
  }
}
Example 3: Prima Default (Built-in Database)
{
  "LdapConfiguration": {
    "AuthenticationType": "Prima"
  },
  "JwtTokenSettings": {
    "Secret": "your-production-secret-key-at-least-32-chars",
    "RefreshTokenTTL": 7
  }
}
Example 4: Single Sign-On with Okta
{
  "LdapConfiguration": {
    "AuthenticationType": "Oidc"
  },
  "OidcConfiguration": {
    "Provider": "Okta",
    "DisplayName": "Sign in with Okta",
    "OktaDomain": "mycompany.okta.com",
    "OktaAuthorizationServerId": "default",
    "ClientId": "0oab1234567890ABC",
    "ClientSecret": "your-client-secret"
  }
}
Example 5: Single Sign-On with Microsoft Entra ID (LDAP-style user names)
{
  "LdapConfiguration": {
    "AuthenticationType": "Oidc"
  },
  "OidcConfiguration": {
    "Provider": "Entra",
    "DisplayName": "Sign in with Microsoft",
    "EntraTenantId": "company.com",
    "ClientId": "00000000-0000-0000-0000-000000000000",
    "ClientSecret": "your-client-secret",
    "UserMatchIgnoreDomain": true
  }
}
Tip: After making authentication configuration changes, restart the Prima Windows service for changes to take effect.

Image Storage

Configure where Prima stores digital images including cassette images, slide images, specimen photos, and scanned documents. Prima supports four storage modes: File (local/network paths), S3 (Amazon S3 or compatible services), Azure Blob (Microsoft Azure Blob Storage), and Advanced (different storage per image type).

Storage Modes Overview

Prima uses the first mode it finds configured, in this order: File (when RootDirectory is set), S3 (when BucketName and CredentialProfile are set), AzureBlob (when ConnectionString and ContainerName are set), then Advanced. Other modes are ignored without any error.

Switching modes: the installer writes a File block by default, and it wins over everything else. When you move to S3, Azure Blob, or Advanced, delete the File block from ImageStorage. The installer keeps your ImageStorage section as you set it on later upgrades.
Mode Use Case Configuration Key
File Local disk or network share storage (simplest option) ImageStorage.File
S3 Cloud storage using Amazon S3 or compatible services (Digital Ocean Spaces, MinIO) ImageStorage.S3
Azure Blob Cloud storage using Microsoft Azure Blob Storage ImageStorage.AzureBlob
Advanced Different storage location per image type (e.g., slides in S3, documents on network share) ImageStorage.Advanced

File Storage Mode

The simplest configuration stores all images in subdirectories under a single root path. The installer writes this default:

{
  "ImageStorage": {
    "File": {
      "RootDirectory": "C:\\ProgramData\\FortelineaSoftwareSystems\\Prima\\{AEE7C64D-4351-40B6-90E2-6A250698A640}\\Storage"
    }
  }
}

Prima automatically creates subdirectories for each image type:

  • CassetteImages/ - Photos of cassettes
  • SlideImages/ - Whole slide images
  • SpecimenImages/ - Specimen gross photos
  • Documents/ - Scanned documents and attachments
Installed servers: folders come from ImageStorageDirectories. In File mode, the top-level ImageStorageDirectories section (SlideImageStorageDirectory, SpecimenImageStorageDirectory, CassetteImageStorageDirectory, ScannedDocumentStorageDirectory) overrides the folder for each image type it names, and the installer rewrites it from its storage folder settings on every install and upgrade. To move image storage on an installed server, change the storage folders in the Prima Server Installer. Editing RootDirectory only affects image types that have no ImageStorageDirectories entry.
Network Shares: You can use a UNC path for network storage: \\\\server\\share\\PrimaStorage (note the escaped backslashes in JSON). The web service's account needs modify rights on the share.

S3 Storage Mode

Store all images in an Amazon S3 bucket or S3-compatible service (Digital Ocean Spaces, MinIO, etc.).

Step 1: Define Credential Profiles

First, create one or more credential profiles that can be referenced by storage configurations:

{
  "StorageCredentialProfiles": [
    {
      "Name": "aws-production",
      "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
      "SecretAccessKey": "${secrets:env:AWS_SECRET_KEY}",
      "Region": "us-east-1",
      "Endpoint": null
    },
    {
      "Name": "digitalocean-spaces",
      "AccessKeyId": "DO00EXAMPLE123",
      "SecretAccessKey": "${secrets:env:DO_SPACES_SECRET}",
      "Region": "nyc3",
      "Endpoint": "https://nyc3.digitaloceanspaces.com"
    }
  ]
}
Property Description
Name Unique identifier for referencing this profile
AccessKeyId AWS access key ID or equivalent for S3-compatible services
SecretAccessKey Secret key (supports secret resolution syntax). AccessKeyId does not.
Region AWS region (e.g., us-east-1). Required for Amazon S3; ignored when Endpoint is set.
Endpoint Service URL for S3-compatible services (leave null for Amazon S3). When set, Prima ignores Region and always uses path-style addressing (https://endpoint/bucket/key).
Step 2: Configure S3 Storage
{
  "ImageStorage": {
    "S3": {
      "CredentialProfile": "aws-production",
      "BucketName": "prima-images",
      "Prefix": "prod/"
    }
  }
}
Property Description
CredentialProfile Name of a credential profile defined in StorageCredentialProfiles
BucketName S3 bucket name
Prefix Optional prefix for all object keys (useful for multi-tenant or environment separation). A trailing / is ignored.

Objects are stored under {Prefix}/cassette-images/, slide-images/, specimen-images/, and documents/ (and label-templates/ for label templates). These names differ from the File mode folder names (CassetteImages, SlideImages, and so on), so rename the folders when you copy existing images into a bucket.

S3 Settings (Optional)

Optional; the values below are the defaults. TimeoutSeconds applies to S3 requests only. MaxRetryAttempts and RetryDelayMilliseconds apply to both S3 and Azure Blob; the delay doubles on each retry (500 ms, 1 s, 2 s, ...).

{
  "S3Settings": {
    "TimeoutSeconds": 30,
    "MaxRetryAttempts": 3,
    "RetryDelayMilliseconds": 500
  }
}

Azure Blob Storage Mode

Store all images in a Microsoft Azure Blob Storage container.

{
  "ImageStorage": {
    "AzureBlob": {
      "ConnectionString": "${secrets:env:AZURE_STORAGE_CONNECTION_STRING}",
      "ContainerName": "prima-images",
      "Prefix": "prod/"
    }
  }
}
Property Description
ConnectionString Azure Storage account connection string (supports secret resolution syntax)
ContainerName Azure Blob container name
Prefix Optional prefix for all blob names (useful for multi-tenant or environment separation). Blobs use the same layout as S3: {Prefix}/cassette-images/, slide-images/, specimen-images/, documents/.
Connection String Format: You can find your connection string in the Azure Portal under Storage Account → Access Keys. The format is: DefaultEndpointsProtocol=https;AccountName=yourAccount;AccountKey=yourKey;EndpointSuffix=core.windows.net

Advanced Storage Mode

Configure different storage locations for each image type. This is useful for hybrid scenarios, such as keeping high-volume slide images in cloud storage while keeping documents on a local network share.

{
  "ImageStorage": {
    "Advanced": {
      "CassetteImage": {
        "Type": "File",
        "Path": "C:\\Storage\\Cassettes"
      },
      "SlideImage": {
        "Type": "S3",
        "CredentialProfile": "aws-production",
        "BucketName": "prima-slides",
        "Prefix": ""
      },
      "SpecimenImage": {
        "Type": "AzureBlob",
        "ConnectionString": "${secrets:env:AZURE_STORAGE_CONNECTION_STRING}",
        "ContainerName": "prima-specimens",
        "Prefix": ""
      },
      "ScannedDocument": {
        "Type": "File",
        "Path": "\\\\server\\share\\Documents"
      }
    }
  }
}

Each image type can be configured as File, S3, or AzureBlob. Type must be spelled exactly like that (it is case-sensitive).

File Target
{ "Type": "File", "Path": "C:\\path\\to\\storage" }
S3 Target
{ "Type": "S3", "CredentialProfile": "name", "BucketName": "bucket", "Prefix": "optional/" }
Azure Blob Target
{ "Type": "AzureBlob", "ConnectionString": "...", "ContainerName": "container", "Prefix": "optional/" }
Configure all four image types (CassetteImage, SlideImage, SpecimenImage, and ScannedDocument). A type left out has no storage at all, and the first attempt to save or read one fails. A File target uses Path as-is, with no subfolder added, so give each type its own folder. Advanced mode does not apply to label templates.

Secret Resolution

To avoid storing sensitive credentials in plain text, use secret resolution syntax. It works in exactly two places: a credential profile's SecretAccessKey and an Azure Blob ConnectionString (including Advanced targets). The reference must be the whole value, for example "${secrets:env:AWS_SECRET_KEY}", not part of a longer string. Provider names are not case-sensitive. Prima supports these secret providers:

Provider Syntax Example
Environment Variable ${secrets:env:VARIABLE_NAME} ${secrets:env:AWS_SECRET_KEY}
Azure Key Vault ${secrets:azure-keyvault:secret-name} ${secrets:azure-keyvault:prima-s3-secret}
AWS Secrets Manager ${secrets:aws-secrets:secret-name} ${secrets:aws-secrets:prima-s3-secret-key}
Plain Text (no prefix) wJalrXUtnFEMI/K7MDENG/bPxRfi...
Configuring Secret Providers

If using Azure Key Vault or AWS Secrets Manager, configure the provider connection:

{
  "SecretProviders": {
    "AzureKeyVault": {
      "VaultUri": "https://my-vault.vault.azure.net/"
    },
    "AwsSecretsManager": {
      "Region": "us-east-1"
    }
  }
}
  • Environment variables: the variable must be visible to the Windows service, so set it as a system-level variable and restart the service. A missing or empty variable stops storage from starting.
  • Azure Key Vault signs in with the Azure default credential chain (for example a managed identity, or environment variables for a service principal) under the web service's account.
  • AWS Secrets Manager uses the default AWS credential chain of the web service's account, not a storage credential profile. Store the secret as plain text: Prima uses the whole secret string, so a key/value (JSON) secret would be passed on as JSON.
Security Best Practice: Never commit plain text credentials to source control. Use environment variables or a secrets manager for production deployments.

Complete Configuration Example

Here's a complete example using S3 storage with environment variable secrets:

{
  "StorageCredentialProfiles": [
    {
      "Name": "production-s3",
      "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
      "SecretAccessKey": "${secrets:env:PRIMA_S3_SECRET_KEY}",
      "Region": "us-east-1",
      "Endpoint": null
    }
  ],
  "ImageStorage": {
    "S3": {
      "CredentialProfile": "production-s3",
      "BucketName": "my-prima-images",
      "Prefix": "production/"
    }
  },
  "S3Settings": {
    "TimeoutSeconds": 30,
    "MaxRetryAttempts": 3,
    "RetryDelayMilliseconds": 500
  }
}

S3-Compatible Services

Prima works with any S3-compatible storage service. Set the credential profile's Endpoint property to the service's S3 API URL (Region is then ignored):

Service Endpoint Example
Amazon S3 null (uses default AWS endpoints)
Digital Ocean Spaces https://nyc3.digitaloceanspaces.com
MinIO https://minio.example.com:9000
Backblaze B2 https://s3.us-west-000.backblazeb2.com

Label Template Storage

Label templates (current.ltp) have their own section, LabelTemplateStorage, with the same File, S3, and AzureBlob options as ImageStorage (not Advanced). Without it, Prima keeps label templates where it always has: C:\ProgramData\FortelineaSoftwareSystems\Prima\{AEE7C64D-4351-40B6-90E2-6A250698A640}\LabelTemplates. If you move images to the cloud, move label templates too:

{
  "LabelTemplateStorage": {
    "S3": {
      "CredentialProfile": "aws-production",
      "BucketName": "prima-label-templates"
    }
  }
}

All-Users Data Folder

Other server files (client installers, the default label template folder above, and similar) live under C:\ProgramData\FortelineaSoftwareSystems\Prima\{APP-GUID}. Set DataStorage:AllUsersRoot to move the C:\ProgramData part elsewhere; the folders below it stay the same. It must be an absolute path, or the web service will not start. Leave it empty to use the default.

{
  "DataStorage": {
    "AllUsersRoot": "D:\\PrimaData"
  }
}
Tip: After making storage configuration changes, restart the Prima Windows service for changes to take effect.

Web Server (Kestrel)

Configure the ASP.NET Core Kestrel web server that hosts Prima's web interface and API.

Endpoints and HTTPS Certificate

The Prima Server Installer writes the Kestrel section from its ports and certificate settings:

{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://+:80",
        "Protocols": "Http1"
      },
      "Https": {
        "Url": "https://+:443",
        "Protocols": "Http1AndHttp2",
        "Certificate": {
          "Subject": "prima.company.com",
          "Store": "My",
          "Location": "LocalMachine",
          "AllowInvalid": "false"
        }
      }
    }
  }
}
Property Description
Url The address and port each endpoint listens on. + means every network interface.
Protocols The HTTPS endpoint must allow HTTP/2 (Http1AndHttp2): the desktop applications talk to Prima over gRPC, which needs it.
Certificate The HTTPS certificate, found by Subject in the Store / Location certificate store. With AllowInvalid "false" the certificate must be valid and trusted by the server itself. Kestrel also accepts Path and Password for a PFX file.
Change ports and the certificate in the installer. Every install or upgrade rewrites the Kestrel section from the installer's settings. A hand-edited Url survives only if its port still matches the installer's; the Certificate block is always replaced.

Request Size Limit

{
  "MaxRequestSizeInBytes": 52428800
}

The largest request body the server accepts, in bytes (default 52428800, 50 MB), which limits upload size. This top-level setting is required: the web service will not start without it. The installer writes the default only when it is missing or zero, and otherwise keeps your value.

Connection timeouts (keep-alive 35 seconds, request headers 1 minute, HTTP/2 keep-alive pings) are fixed in Prima and are not configurable.

Load Balancers and Proxies

When Prima runs behind a load balancer or reverse proxy that terminates HTTPS, turn on forwarded headers so Prima sees each request's original HTTPS address and client IP. Single sign-on needs this to build its redirect address.

{
  "ForwardedHeadersOptions": {
    "Enabled": true,
    "ForwardedHeaders": "XForwardedFor, XForwardedProto",
    "KnownProxies": [ "10.0.0.5" ],
    "KnownNetworks": [ { "Prefix": "10.0.0.0", "PrefixLength": 24 } ]
  }
}
Property Description
Enabled Turns forwarded header processing on (default false). While it is on, Prima does not redirect HTTP to HTTPS or send HSTS itself; the load balancer should.
ForwardedHeaders Which headers to honor; set "XForwardedFor, XForwardedProto". Without it no headers are processed.
KnownProxies / KnownNetworks The load balancer's IP addresses, or networks as Prefix and PrefixLength. When both are empty, the headers are accepted from any address, so list your load balancer where possible.

Also set TokenServer:Issuer to the public address users reach (see the Authentication tab), and keep the cookie Secure policy at "Always".

Tip: After making web server configuration changes, restart the Prima Windows service for changes to take effect.

Database Connection

Configure the connection to your SQL Server database.

Connection String

Prima reads one connection string, PrimaProduction:

{
  "ConnectionStrings": {
    "PrimaProduction": "Data Source=sqlserver.company.com;Initial Catalog=Prima;Integrated Security=True;Encrypt=True;TrustServerCertificate=False;Application Name=PrimaWebService"
  }
}

It is a standard Microsoft SQL Server (SqlClient) connection string, so the usual keywords apply, for example Connect Timeout, Max Pool Size, Encrypt, and TrustServerCertificate. With Integrated Security=True (Windows authentication), Prima connects as the web service's account, which needs read and write access to the database. With SQL authentication, use User ID and Password instead.

Change the connection in the installer. Every install or upgrade rewrites PrimaProduction from the installer's Database page, so a hand-edited connection string is replaced at the next upgrade.
Tip: After changing the connection string, restart the Prima Windows service for the change to take effect.