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).
The configuration uses the standard ASP.NET Core configuration system. Settings can be overridden using:
appsettings.json - Base configurationappsettings.{Environment}.json - Optional overrides for the environment named by ASPNETCORE_ENVIRONMENT (Production when unset)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.
Use the tabs on the left to learn about configuring each section:
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).
| 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 |
When multiple authentication methods are configured, Prima uses this priority order:
LdapConfiguration has AuthenticationType: "Oidc" and OidcConfiguration is fully configuredOktaConfiguration is fully configuredLdapConfiguration has AuthenticationType: "Ldap" and names an LDAP server (Host set to anything other than localhost; see LDAP)
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.
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.
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) |
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).
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.
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.
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"
}
}
| 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).
|
| 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" |
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.
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.
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 |
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):
common / organizations addresses.
openid, profile, and email
(Microsoft Graph, delegated).
Okta (Admin Console):
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://).
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).
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).
preferred_username is the user principal name, e.g. jsmith@company.com.preferred_username is the Okta username, usually the e-mail address.jsmith (common for sites that used LDAP), set
UserMatchIgnoreDomain to true (Ignore the domain when matching in the installer).
jsmith@company.com), or the part before the @ with UserMatchIgnoreDomain turned on.
Otherwise nobody can sign in to set up everyone else.
DisplayName, and no user name and
password boxes.
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.
Single sign-on refused). Create the user with that user name, or adjust UserMatchClaims /
UserMatchIgnoreDomain.
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.
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.
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"
}
}
| 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 |
| 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.
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.
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.
OktaConfiguration section of appsettings.json as shown above.
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.
https://{Domain}/oauth2/{AuthorizationServerId}/v1/token
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" |
"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.
{
"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
}
}
{
"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
}
}
{
"LdapConfiguration": {
"AuthenticationType": "Prima"
},
"JwtTokenSettings": {
"Secret": "your-production-secret-key-at-least-32-chars",
"RefreshTokenTTL": 7
}
}
{
"LdapConfiguration": {
"AuthenticationType": "Oidc"
},
"OidcConfiguration": {
"Provider": "Okta",
"DisplayName": "Sign in with Okta",
"OktaDomain": "mycompany.okta.com",
"OktaAuthorizationServerId": "default",
"ClientId": "0oab1234567890ABC",
"ClientSecret": "your-client-secret"
}
}
{
"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
}
}
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).
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.
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 |
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 cassettesSlideImages/ - Whole slide imagesSpecimenImages/ - Specimen gross photosDocuments/ - Scanned documents and attachmentsImageStorageDirectories. 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.
\\\\server\\share\\PrimaStorage (note the escaped backslashes in JSON). The web service's account
needs modify rights on the share.
Store all images in an Amazon S3 bucket or S3-compatible service (Digital Ocean Spaces, MinIO, etc.).
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). |
{
"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.
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
}
}
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/. |
DefaultEndpointsProtocol=https;AccountName=yourAccount;AccountKey=yourKey;EndpointSuffix=core.windows.net
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).
{ "Type": "File", "Path": "C:\\path\\to\\storage" }
{ "Type": "S3", "CredentialProfile": "name", "BucketName": "bucket", "Prefix": "optional/" }
{ "Type": "AzureBlob", "ConnectionString": "...", "ContainerName": "container", "Prefix": "optional/" }
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.
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... |
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"
}
}
}
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
}
}
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 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"
}
}
}
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"
}
}
Configure the ASP.NET Core Kestrel web server that hosts Prima's web interface and API.
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.
|
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.
{
"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.
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".
Configure the connection to your SQL Server database.
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.
PrimaProduction
from the installer's Database page, so a hand-edited connection string is replaced at the next upgrade.