Skip to content
Version v3.0.0

Migrating from ProAuth 2.x to 3.x

Breaking Changes

ProAuth 3.x includes several breaking changes that require manual action. Review each section below carefully before upgrading.

Database Migration For Existing 2.x Installations

Existing ProAuth 2.x installations use SQL Server databases in the dbo schema. ProAuth 3.x uses EF Core migrations and schema-local objects: ProAuth objects are stored in the proauth schema and UserStore objects are stored in the userstore schema.

Before upgrading an existing installation, first update to the latest ProAuth 2.x release. Then migrate the SQL Server databases with the v2-to-v3 bridge command from the ProAuth 3.x CLI. This migration is a planned downtime step.

Backup Required

Take a verified backup of the ProAuth database and every UserStore database before running the bridge migration. Stop ProAuth 2.x or otherwise block writes while the migration and Helm upgrade are running.

The bridge migration:

  • migrates the ProAuth database first and then all discovered UserStore databases;
  • creates the proauth and userstore schemas;
  • moves existing tables from dbo to the new schemas without copying table data;
  • applies the ProAuth 3.x schema changes, including MFA table and column renames, RBAC tables, passkey tables, and concurrency token columns;
  • rewrites existing label content paths from the legacy /label and /page roots to the ProAuth 3.x scoped roots;
  • drops the legacy _MigrationScriptsHistory table after a successful migration;
  • creates the schema-local EF Core migration history table and stamps the InitialV3 migration so future upgrades continue through EF Core migrations.

The v3 initial schema also records normalized certificate storage metadata for CertStore rows. Existing v2 certificates remain readable and are marked as PKCS#12; rows with an existing password are marked as password-protected, and rows without one are marked as unprotected. No customer action is required for existing base64 PFX certificates.

The command is idempotent. If a UserStore database fails, the error is logged and the migration continues with the next UserStore database. After fixing the cause, run the command again to migrate the remaining databases.

SQL Server Only

The v2-to-v3 bridge migration supports SQL Server source and SQL Server target databases only. Direct migration from SQL Server 2.x databases to PostgreSQL 3.x databases is not part of this upgrade path. PostgreSQL can be used for fresh ProAuth 3.x installations or for a separate customer-specific data migration project.

Example command:

bash
proauthcli migrate v2tov3 \
  --provider SqlServer \
  --connectionstring '<ProAuth SQL Server connection string>' \
  --user '<schema deployment user>' \
  --password '<schema deployment password>' \
  --relateddbquery "SELECT [Value] FROM [dbo].[Option] WHERE [ApplyToIdpInstanceId] IN (SELECT [Id] FROM [dbo].[IdpInstance] WHERE [IdpTypeId] = (SELECT [Id] FROM [dbo].[IdpType] WHERE [FactoryType] = 'ProAuth.IdpModules.UserStore.UserStoreIdpInstanceFactory, ProAuth.IdpModules.UserStore')) AND [Name] = 'ConnectionString'" \
  --connectionstringalias 'DefaultUserStoreConnection|Server=<server>;User Id=<deployment user>;Password=<deployment password>;TrustServerCertificate=True' \
  --connectionstringalias 'AnotherAlias|Server=<server>;User Id=<deployment user>;Password=<deployment password>;TrustServerCertificate=True'

Use --connectionstringalias for every alias that can appear in a UserStore connection string. The alias name is the part before |; the value after | is the root connection string used to resolve the database from the stored UserStore connection string.

After the bridge migration succeeds, deploy ProAuth 3.x with Helm and keep the provider set to SQL Server:

yml
appsettings:
  data:
    defaultconnection:
      provider: SqlServer
      connectionstring: VALUE_TO_OVERRIDE
      commandtimeoutinseconds: 30

The regular Helm initializer job then runs before the ProAuth application update. For a successfully bridged database it should find the stamped InitialV3 migration and only apply later EF Core migrations when they exist.

You can verify the EF migration history with:

sql
SELECT * FROM [proauth].[__EFMigrationsHistory];
SELECT * FROM [userstore].[__EFMigrationsHistory];

Helm Secret Migration

ProAuth 3.x changes the Helm Secret structure for both the ProAuth Core chart and the ProAuth Admin App chart. This is a breaking change for installations that pre-create Kubernetes Secrets and enable externalSecrets.

The new structure keeps confidential values in Secrets and uses direct .NET configuration environment variable names as Secret keys. Non-secret settings should be supplied through Helm values or through a pre-created appsettings.overrides.json ConfigMap with externalConfig.

Manual Secret Migration Required

If you pre-create Secrets for ProAuth 2.x, create new v3 Secrets before upgrading the Helm release. The v3 chart does not read the old Secret group names or old lowercase keys.

ProAuth Core Secret Groups

ProAuth 2.x external secretProAuth 3.x external secretNotes
userstoreconnectionsaliasesruntimeAlias keys move to Data__UserStoreConnections__ConnectionStringAliases__<alias>
datadefaultconnectionruntimeOnly the connection string remains secret
licenseruntimeLicense data remains secret
baseservicesettingsruntime or non-secret configOnly mailserverconfig remains in Secret as BaseServiceSettings__MailServerConfig
proauthrootcryptoIncludes client app secret, SCIM token key, and default signing certificate
encryptionkeyscryptoCertificate material and passwords remain secret; mode is non-secret
dbschemadeploymentdbSchemaDeploymentKeys are renamed to direct env-var names
dbdeploymentworkerSqlserverdbWorkerSqlserverCredentials remain secret; containment is non-secret
dbdeploymentworkerAzuresqldbWorkerAzuresqlCredentials remain secret; Azure resource identifiers and auth mode are non-secret
dbdeploymentworkerPostgresqldbWorkerPostgresqlCredentials remain secret; admindatabase is non-secret

ProAuth Core Key Mapping

ProAuth 2.x secret/keyProAuth 3.x secret/key
datadefaultconnection.connectionstringruntime.Data__DefaultConnection__ConnectionString
datadefaultconnection.providerHelm value or appsettings.overrides.json: Data:DefaultConnection:Provider
datadefaultconnection.commandtimeoutinsecondsHelm value or appsettings.overrides.json: Data:DefaultConnection:CommandTimeoutInSeconds
userstoreconnectionsaliases.<alias>runtime.Data__UserStoreConnections__ConnectionStringAliases__<alias>
license.licensedataruntime.License__LicenseData
baseservicesettings.mailserverconfigruntime.BaseServiceSettings__MailServerConfig
baseservicesettings.hosturlHelm value or appsettings.overrides.json: BaseServiceSettings:HostUrl
baseservicesettings.emailsenderaddressHelm value or appsettings.overrides.json: BaseServiceSettings:EmailSenderAddress
baseservicesettings.requirehttpsmetadataHelm value or appsettings.overrides.json: BaseServiceSettings:RequireHttpsMetadata
baseservicesettings.sessionidletimeoutHelm value or appsettings.overrides.json: BaseServiceSettings:SessionIdleTimeout
baseservicesettings.jobqueueintervalHelm value or appsettings.overrides.json: BaseServiceSettings:JobQueueInterval
baseservicesettings.forwardlimitHelm value or appsettings.overrides.json: BaseServiceSettings:ForwardLimit
baseservicesettings.knownproxiesHelm value or appsettings.overrides.json: BaseServiceSettings:KnownProxies
baseservicesettings.knownnetworksHelm value or appsettings.overrides.json: BaseServiceSettings:KnownNetworks
proauthroot.clientappsecretcrypto.ProAuthRoot__ClientAppSecret
proauthroot.scimtokensecuritykeycrypto.ProAuthRoot__ScimTokenSecurityKey
proauthroot.defaultcertvaluecrypto.ProAuthRoot__DefaultCertValue
proauthroot.defaultcertpasswordcrypto.ProAuthRoot__DefaultCertPassword
encryptionkeys.certificatecrypto.EncryptionKeys__Certificate
encryptionkeys.certificatepasswordcrypto.EncryptionKeys__CertificatePassword
encryptionkeys.modeHelm value or appsettings.overrides.json: EncryptionKeys:Mode
encryptionkeys.KeyRotationDecryptionCertificates__<index>__Certificatecrypto.EncryptionKeys__KeyRotationDecryptionCertificates__<index>__Certificate
encryptionkeys.KeyRotationDecryptionCertificates__<index>__CertificatePasswordcrypto.EncryptionKeys__KeyRotationDecryptionCertificates__<index>__CertificatePassword
dbschemadeployment.userdbSchemaDeployment.ProAuthDbSchemaDeployment__User
dbschemadeployment.passworddbSchemaDeployment.ProAuthDbSchemaDeployment__Password
dbdeploymentworkerSqlserver.connectionstringdbWorkerSqlserver.SqlServer__ConnectionString
dbdeploymentworkerSqlserver.dbuserdbWorkerSqlserver.SqlServer__DbUser
dbdeploymentworkerSqlserver.dbpassworddbWorkerSqlserver.SqlServer__DbPassword
dbdeploymentworkerSqlserver.deploymentuserdbWorkerSqlserver.SqlServer__DeploymentUser
dbdeploymentworkerSqlserver.deploymentpassworddbWorkerSqlserver.SqlServer__DeploymentPassword
dbdeploymentworkerSqlserver.containmentHelm value or appsettings.overrides.json: SqlServer:Containment
dbdeploymentworkerAzuresql.clientsecretdbWorkerAzuresql.AzureSql__ClientSecret
dbdeploymentworkerAzuresql.sqladminuserdbWorkerAzuresql.AzureSql__SqlAdminUser
dbdeploymentworkerAzuresql.sqladminpassworddbWorkerAzuresql.AzureSql__SqlAdminPassword
dbdeploymentworkerAzuresql.dbuserdbWorkerAzuresql.AzureSql__DbUser
dbdeploymentworkerAzuresql.dbpassworddbWorkerAzuresql.AzureSql__DbPassword
dbdeploymentworkerAzuresql.deploymentuserdbWorkerAzuresql.AzureSql__DeploymentUser
dbdeploymentworkerAzuresql.deploymentpassworddbWorkerAzuresql.AzureSql__DeploymentPassword
dbdeploymentworkerPostgresql.connectionstringdbWorkerPostgresql.PostgreSql__ConnectionString
dbdeploymentworkerPostgresql.admindatabaseHelm value or appsettings.overrides.json: PostgreSql:AdminDatabase
dbdeploymentworkerPostgresql.dbuserdbWorkerPostgresql.PostgreSql__DbUser
dbdeploymentworkerPostgresql.dbpassworddbWorkerPostgresql.PostgreSql__DbPassword
dbdeploymentworkerPostgresql.deploymentuserdbWorkerPostgresql.PostgreSql__DeploymentUser
dbdeploymentworkerPostgresql.deploymentpassworddbWorkerPostgresql.PostgreSql__DeploymentPassword

ProAuth Admin App Secret Groups

ProAuth Admin App 2.x external secretProAuth Admin App 3.x external secretNotes
authenticationruntimeOnly confidential authentication values remain secret
initializersettingsresourceDeploymentOnly the resource deployment client secret remains secret
encryptionkeyscryptoCertificate material and passwords remain secret; mode is non-secret
baseservicesettingsnon-secret configMove to Helm values or appsettings.overrides.json

ProAuth Admin App Key Mapping

ProAuth Admin App 2.x secret/keyProAuth Admin App 3.x secret/key
authentication.clientsecretruntime.AuthenticationSettings__ClientSecret
authentication.dpop__jsonwebkeyruntime.AuthenticationSettings__Dpop__JsonWebKey
authentication.clientassertion__certificateruntime.AuthenticationSettings__ClientAssertion__Certificate
authentication.clientassertion__certificatepasswordruntime.AuthenticationSettings__ClientAssertion__CertificatePassword
authentication.mutualtls__certificateruntime.AuthenticationSettings__MutualTls__Certificate
authentication.mutualtls__certificatepasswordruntime.AuthenticationSettings__MutualTls__CertificatePassword
authentication.requestobject__certificateruntime.AuthenticationSettings__RequestObject__Certificate
authentication.requestobject__certificatepasswordruntime.AuthenticationSettings__RequestObject__CertificatePassword
authentication.authorityHelm value or appsettings.overrides.json: AuthenticationSettings:Authority
authentication.clientidHelm value or appsettings.overrides.json: AuthenticationSettings:ClientId
authentication.tenantidHelm value or appsettings.overrides.json: AuthenticationSettings:TenantId
initializersettings.resourceclientsecretresourceDeployment.InitializerSettings__ResourceClientSecret
initializersettings.serviceurlHelm value or appsettings.overrides.json: InitializerSettings:ServiceUrl
initializersettings.resourceclientidHelm value or appsettings.overrides.json: InitializerSettings:ResourceClientId
initializersettings.resourcetenantidHelm value or appsettings.overrides.json: InitializerSettings:ResourceTenantId
encryptionkeys.certificatecrypto.EncryptionKeys__Certificate
encryptionkeys.certificatepasswordcrypto.EncryptionKeys__CertificatePassword
encryptionkeys.modeHelm value or appsettings.overrides.json: EncryptionKeys:Mode
baseservicesettings.*Helm values or appsettings.overrides.json under BaseServiceSettings

Example v3 External Secret Values

yaml
externalSecrets:
  runtime:
    enabled: true
    secretName: proauth-runtime
    keys:
      - Data__DefaultConnection__ConnectionString
      - Data__UserStoreConnections__ConnectionStringAliases__DefaultUserStoreConnection
      - License__LicenseData
  crypto:
    enabled: true
    secretName: proauth-crypto
    keys:
      - ProAuthRoot__ClientAppSecret
      - ProAuthRoot__ScimTokenSecurityKey
      - ProAuthRoot__DefaultCertValue
      - ProAuthRoot__DefaultCertPassword
      - EncryptionKeys__Certificate
      - EncryptionKeys__CertificatePassword

For non-secret pre-created settings:

yaml
externalConfig:
  enabled: true
  existingConfigMap: proauth-overrides
  key: appsettings.overrides.json
  mountPath: /app

Helm Resource Name And Label Migration

ProAuth 3.x also standardizes Helm template filenames, Kubernetes resource names, and labels. This is a breaking change for automation that references Kubernetes objects by name or selects pods by the legacy labels.

The charts now use the recommended Kubernetes label set:

yaml
helm.sh/chart
app.kubernetes.io/name
app.kubernetes.io/instance
app.kubernetes.io/version
app.kubernetes.io/managed-by
app.kubernetes.io/part-of
app.kubernetes.io/component

Update scripts, dashboards, kubectl commands, NetworkPolicies, ServiceMonitors, PodMonitors, and custom wait logic that used labels such as app=proauth, app=proauthadminapp, or release=<release>.

Common selectors:

bash
# ProAuth Core runtime pods
app.kubernetes.io/name=proauth,app.kubernetes.io/component=identity-server

# ProAuth DB deployment worker pods
app.kubernetes.io/name=proauth,app.kubernetes.io/component=db-deployment-worker

# ProAuth Admin App pods
app.kubernetes.io/name=proauthadminapp,app.kubernetes.io/component=admin-app

ProAuth Core Resource Names

ProAuth 2.x resourceProAuth 3.x resource
proauth-service-account<fullname>
proauth-deploy-service-account<fullname>-deploy
proauth-db-management-service-account<fullname>-db-management
k8s-wait-for-<fullname><fullname>-wait-for
<fullname>-dbdeployment-<version-or-revision><fullname>-initializer-<version-or-revision>
<fullname>-dbdeploymentworker<fullname>-db-deployment-worker
<fullname>-azure-id-binding<fullname>-azure-identity-binding
<fullname>-dbdeploy-azure-id-binding<fullname>-initializer-azure-identity-binding
proauthdatabasedeploymentuserstoreworker Dapr app id<fullname>-db-deployment-worker
proauthdatabasedeploymentuserstoreworker default Dapr component nameproauth-db-deployment-worker-pubsub

The ProAuth Core chart also renamed internal template files, for example dbdeploymentjob.yaml to initializer-job.yaml and statefulsetDbDeploymentWorker.yaml to db-deployment-worker-statefulset.yaml. Template filenames are not Kubernetes API resources, but they appear in helm template output comments and snapshot-based tooling.

ProAuth Admin App Resource Names

ProAuth Admin App 2.x resourceProAuth Admin App 3.x resource
proauthadminapp-service-account<fullname>
proauthadminapp-deploy-service-account<fullname>-resource-deployment
k8s-wait-for-<fullname><fullname>-wait-for
<fullname>-resourcesdeployment-<version-or-revision><fullname>-resource-deployment-<version-or-revision>
proauthadminapp-resourcesdeployment containerproauthadminapp-resource-deployment container

The Admin App chart now waits for ProAuth Core with the selector app.kubernetes.io/name=proauth,app.kubernetes.io/component=identity-server.

Overview

ProAuth 3.x modernizes the authentication view system, introduces a new CQRS-lite v2 API with an improved permission model, and provides enhanced security and accessibility.

Area2.x3.x
Management & User Store APIsv1 onlyv1 (deprecated) + v2 (recommended)
API patternTraditional CRUD DTOsCQRS-lite with typed commands
.NET API identifiersGeneric Guid valuesDomain-specific typed IDs in v2 clients
Permission modelFlag-based ProAuthSecurityRole enumRole-based access control (RBAC) with scoped permissions
Client librariesProAuth.Clients.Management / .UserStorev2 packages: ProAuth.Clients.Management.V2 / .UserStore.V2
CLI YAML importsYAML tags and v1 DTO shapes, for example !CustomerDtoAPI-versioned resources with apiVersion, kind, metadata, spec, and relationships
Template engineRazor (.cshtml)Fluid/Liquid (.liquid)
CSS frameworkBootstrap + jQueryTailwind CSS + vanilla JS
Form stateHidden form fields in HTMLServer-side auth flow session
MFA terminologyTwoFactor / SecondFactor namesMfa / MfaFactor names
Code executionArbitrary C# in templatesBuilding block tags only
Template validationNoneSyntax validated on save via API

Backward Compatibility

API v1 endpoints remain fully functional in ProAuth 3.x. Your existing integrations will continue to work without changes. However, v1 is deprecated and will be removed in a future major release. We strongly encourage migrating to v2 at your earliest convenience.

Pre-Upgrade Checklist

Before upgrading, assess the following:

  • [ ] Custom views — Do you have custom view overrides in the database? Export them via the Management API or Admin UI for reference during migration.
  • [ ] Custom assets — CSS and image assets are preserved as-is. However, Bootstrap-specific CSS classes will no longer apply — plan to update custom stylesheets to use CSS custom properties.
  • [ ] CLI YAML files — Convert existing data import, label, and view definition YAML files to the ProAuth 3.x resource schema with the ProAuth CLI before using them with ProAuth 3.x. The database bridge migrates stored label entries automatically, but label and view definition files in configuration repositories must be converted before import.
  • [ ] MFA configuration and imports — Update external settings, Helm values, appsettings overrides, CLI import files, scripts, and v2 API consumers from legacy TwoFactor / SecondFactor names to the new Mfa / MfaFactor names.
  • [ ] Custom MFA views — If you have custom view overrides at MFA paths, recreate them at the renamed v3 paths and convert them to Fluid templates.
  • [ ] API consumers — If you have integrations using the Management API or User Store API, plan your migration to the v2 endpoints. v1 endpoints continue to work but are deprecated. See API Migration below.
  • [ ] Certificate automation — Existing base64 PFX values still work. For new v3 automation, prefer the v2 certificateInput model and Kubernetes mounted Secret files for PEM certificate/private-key pairs.
  • [ ] Client application public keys — ProAuth v3 uses ClientAppKeySet records for private_key_jwt, JAR request-object signing, self_signed_tls_client_auth, and token encryption keys. Configuration repositories and automation must create clientAppKeySets in YAML or call the v2 ClientAppKeySet API; public keys are no longer configured on ClientApp or ClientAppAuthenticationConfiguration.

CLI YAML Migration

ProAuth 3.x changes the YAML import format used by the CLI. Existing ProAuth 2.x YAML files that use tags such as !CustomerDto, !TenantDto, !UserDto, !CustomViewFileImport, or the old label and view definition array formats must be converted before they are imported with ProAuth 3.x.

The old format was based on v1 DTOs and embedded navigation properties. The new format is based on API-versioned resources and v2 APIs:

yaml
apiVersion: management.proauth.net/v2
kind: Tenant
metadata:
  id: "c35e41f0-8ecf-44c4-86f5-2c32bb8b626a"
spec:
  subscriptionId: "ba13af8b-dc96-44af-bb46-fd89b1ee7f12"
  name: "My Tenant"
relationships:
  idpInstances:
    mode: set
    refs:
      - "e8921401-04d5-45d9-80c9-0dce5c4a72bd"

Required Migration Commands

Run these commands on your configuration repository before using the files with ProAuth 3.x:

bash
# Data import files: customers, subscriptions, tenants, client apps, users, groups, roles, custom auth views, custom assets
proauthcli data migrate-yaml "./config/*.yml" --outputPath "./config-v3" --overwrite

# Label import/export files
proauthcli label migrate-yaml "./labels/*.yml" --outputPath "./labels-v3" --overwrite

# Admin UI view definition import/export files
proauthcli viewdefinition migrate-yaml "./viewdefinitions/*.yml" --outputPath "./viewdefinitions-v3" --overwrite

For data imports, keep the default conflict strategy during the first migration:

bash
proauthcli data migrate-yaml "./config/*.yml" --outputPath "./config-v3" --conflictStrategy fail

This stops the migration when incompatible relationship declarations are detected. Use --conflictStrategy preferCanonicalOwner only after you have reviewed the conflict and intentionally want the migration to keep the canonical declaration.

What Changes In The YAML

ProAuth 2.x YAMLProAuth 3.x YAML
Resource type is a YAML tag such as !ClientAppDtoResource type is apiVersion + kind
Entity fields are placed directly at document rootEntity fields are placed under spec
IDs are normal DTO propertiesEntity ID is metadata.id; User Store resources also use metadata.userStoreId
Navigation properties may be nested on either sideRelationships are declared explicitly under relationships
Relationship intent is implicitRelationship intent is mode: set, add, or remove
Legacy TwoFactor* and SecondFactor* names may appearNew files use Mfa* and MfaFactor* names

Label Root Changes

ProAuth 3.x replaces the legacy label roots with scoped roots. This is a breaking change: runtime label lookup no longer falls back to /label or /page, so imports and custom integrations must use the new roots.

ProAuth 2.x root/keyProAuth 3.x root
/page/{rest}/adminapp/{rest}
/label/.../accountmgmt-*/accountmgmt/.../accountmgmt-*
/label/.../account-change-password-*/accountmgmt/.../account-change-password-*
/label/.../request-validation-error-*/oidc/.../request-validation-error-*
/label/.../model-validation-*/system/.../model-validation-*
remaining /label/... entries/authviews/...

The mapping preserves optional customer, subscription, tenant, and app path segments and preserves culture suffixes. For example, /label/tenant/{id}/userlogin-title/de becomes /authviews/tenant/{id}/userlogin-title/de.

The v2-to-v3 database bridge migration rewrites existing Label.ContentPath values with these rules. If both the legacy source and new target already exist, the migration deletes the target first and moves the legacy source so the existing customer translation wins. label migrate-yaml applies the same path mapping to label files, and viewdefinition migrate-yaml rewrites full /page/... label IDs to /adminapp/... while leaving short label IDs unchanged.

Relationship Migration Rules

Relationship migration is the most important behavioral change for configuration-as-code files.

In ProAuth 2.x YAML, relationship handling was ambiguous because some relationships were represented as nested navigation collections, and the same relationship could often be declared from either side. In ProAuth 3.x YAML, relationships are reconciled explicitly:

yaml
relationships:
  memberUsers:
    mode: set
    refs:
      - "{{proauthuserfromuserstore:e8921401-04d5-45d9-80c9-0dce5c4a72bd.6f00f971-1a5c-4f6d-92da-855833f4da9f}}"

The meaning is deterministic:

DeclarationResult
Relationship omittedThe CLI does not manage that relationship. Existing database values remain unchanged.
mode: set with refsThe CLI replaces the relationship set with exactly those refs.
mode: set with refs: []The CLI clears the relationship.
mode: addThe CLI adds the refs and keeps existing values.
mode: removeThe CLI removes the refs and keeps all other existing values.

After migration, review all generated relationships blocks. In particular, look for relationships that were previously declared from both sides, such as tenant-to-IdP and IdP-to-tenant, user-to-group and group-to-user, or group parent/member declarations. Keep one ownership style per relationship in your configuration repository so repeated imports remain easy to reason about.

See YAML Import Schema for the full resource schema, supported relationship owners, and examples.

Management API Migration

ProAuth 3.x introduces Management API v2 alongside the existing v1. The v2 API follows a CQRS-lite pattern with typed commands, type-safe patch operations, and dedicated relationship endpoints.

Base Path Change

v1v2
/api/management/v1/{resource}/api/management/v2/{resource}

DTO Naming Convention Changes

The v2 API uses a clearer naming convention for request and response types:

Operationv1 DTOv2 DTO
Read (GET response)ClientAppDtoClientApp
Create (POST body)ClientAppDtoCreateClientAppCommand
Update (PUT body)ClientAppDtoUpdateClientAppCommand
Patch (PATCH body)(not available)PatchClientAppCommand

New: Type-Safe Patch Operations

v2 introduces PATCH support with dedicated command types. Only include the fields you want to update:

http
PATCH /api/management/v2/clientapp/{id}
Content-Type: application/json

{
  "displayName": "New Name"
}

New: Dedicated Relationship Endpoints

Many-to-many relationships that were previously managed inline as collections now have dedicated sub-resource endpoints:

http
# v1: Relationships were embedded in the entity DTO
PUT /api/management/v1/clientapp/{id}
{ "scopes": [...], "resources": [...] }

# v2: Dedicated relationship endpoints
POST   /api/management/v2/clientapp/{id}/scope
DELETE  /api/management/v2/clientapp/{id}/scope/{scopeId}
GET    /api/management/v2/clientapp/{id}/scope

New: RBAC Permission Model

v2 introduces a proper Role-Based Access Control (RBAC) model replacing the flag-based ProAuthSecurityRole enum:

v1 Conceptv2 Equivalent
ProAuthSecurityRole enum flagsProAuthRole with scoped ProAuthPermission definitions
ProAuthUserRole tableProAuthRoleAssignment with PrincipalType.User
ProAuthGroupRole tableProAuthRoleAssignment with PrincipalType.Group
ClientAppRole tableProAuthRoleAssignment with PrincipalType.ServicePrincipal

The RBAC model uses hierarchical scopes for fine-grained access control:

"*"                             → Global (SystemAdmin)
"customer/{customerId}"         → Customer scope
"subscription/{subscriptionId}" → Subscription scope
"tenant/{tenantId}"             → Tenant scope
"idpinstance/{idpInstanceId}"   → IdP instance scope

New API endpoints for managing the RBAC model:

  • /api/management/v2/proauthrole — Role definitions
  • /api/management/v2/proauthpermission — Permission definitions
  • /api/management/v2/proauthroleassignment — Role-to-principal assignments

Client Library Migration

Update your NuGet package references:

xml
<!-- v1 (deprecated) -->
<PackageReference Include="ProAuth.Clients.Management" Version="..." />

<!-- v2 (recommended) -->
<PackageReference Include="ProAuth.Clients.Management.V2" Version="..." />

Update your using directives:

csharp
// v1
using ProAuth.ManagementServices.Model.Dtos.V1;
using ProAuth.Clients.Management;

// v2
using ProAuth.ManagementServices.Model.Dtos.V2;
using ProAuth.Clients.Management.V2;

Example: Creating a Client App

v1:

http
POST /api/management/v1/clientapp
Content-Type: application/json

{
  "name": "my-app",
  "displayName": "My Application",
  "clientAppType": "Confidential",
  "accessGrantConsentType": "Implicit"
}

v2:

http
POST /api/management/v2/clientapp
Content-Type: application/json

{
  "name": "my-app",
  "displayName": "My Application",
  "clientAppType": "Confidential",
  "accessGrantConsentType": "Implicit"
}

The request structure is similar, but the response uses clean entity names instead of *Dto suffixed types.

New: Typed Identifiers in .NET Clients

The v2 .NET client packages use typed identifiers instead of plain Guid values for API-facing IDs. For example, Management API calls expose values such as ClientAppId, SubscriptionId, TenantId, IdpInstanceId, and ProAuthUserId; User Store API calls expose values such as UserId and GroupId.

The wire format does not change. HTTP paths, JSON payloads, exported configuration, and CLI imports still use UUID strings:

json
{
  "id": "2bf42f1d-24d7-4ce3-b9df-7d27dfb45de0",
  "subscriptionId": "84be971e-6cd7-49e9-90d7-02f9216af6ac"
}

Update .NET integrations that move from v1 or early v2 prerelease clients by converting boundary GUIDs into the matching typed ID once:

csharp
using ProAuth.TypedIds;

SubscriptionId subscriptionId = Guid.Parse("84be971e-6cd7-49e9-90d7-02f9216af6ac");
ClientAppId clientAppId = Guid.Parse("2bf42f1d-24d7-4ce3-b9df-7d27dfb45de0");

Custom OpenAPI generators can either preserve the x-reafx-typed-id metadata or treat those schemas as regular UUID strings. See Typed Identifiers for details.

MFA Naming Migration

ProAuth 3.x standardizes the old TwoFactor and SecondFactor naming to MFA. For fresh 3.x installations, the EF Core initial migration creates the new MFA schema directly. For existing 2.x installations, run the v2-to-v3 bridge migration described above before deploying ProAuth 3.x. Customer-maintained settings files, Helm values, appsettings overrides, scripts, import/export files, and v2 API clients must be reviewed manually.

v1 API compatibility

Management API v1 remains backward compatible. Existing v1 routes, DTOs, JSON properties, and generated v1 clients continue to use the legacy TwoFactor and SecondFactor names. The rename applies to v2 APIs, internal product configuration, modules, Admin UI resources, and new imports/exports.

Concept and API Names

2.x / v1 name3.x / v2 name
TwoFactorMfa
TwoFactorInstanceMfaInstance
TwoFactorTypeMfaType
TwoFactorInstanceMetadataMfaInstanceMetadata
TwoFactorRecoveryCodeMfaRecoveryCode
ProAuthUserSecondFactorProAuthUserMfaFactor
SecondFactorTypeMfaFactorType
SecondFactorValueMfaFactorValue
SecondFactorPasskeyCredentialMfaFactorPasskeyCredential

Management API v2 Routes and Payloads

Update custom v2 integrations and scripts to the new resource names:

Legacy v2 nameProAuth 3.x v2 name
/api/management/v2/TwoFactorInstance/api/management/v2/MfaInstance
/api/management/v2/TwoFactorType/api/management/v2/MfaType
/api/management/v2/TwoFactorInstanceMetadata/{...}/api/management/v2/MfaInstanceMetadata/{...}
/api/management/v2/ProAuthUserSecondFactor/api/management/v2/ProAuthUserMfaFactor
ClientApp/{id}/twofactorinstancesClientApp/{id}/mfainstances
Tenant/{id}/twofactorinstancesTenant/{id}/mfainstances
IdpInstance/{id}/twofactorinstancesIdpInstance/{id}/mfainstances

Common JSON property changes:

Legacy propertyProAuth 3.x property
twoFactorInstancesmfaInstances
twoFactorTypeIdmfaTypeId
applyToTwoFactorInstanceIdapplyToMfaInstanceId
secondFactorTypemfaFactorType
secondFactorValuemfaFactorValue
treatSecondFactorClaimsAsConfirmedtreatMfaFactorClaimsAsConfirmed

Module Configuration

Update module registrations in appsettings.json, Helm values, Kubernetes configuration, and any environment-specific overrides:

2.x module registration3.x module registration
ModuleTwoFactorTwilioModuleMfaTwilio
ModuleTwoFactorECallModuleMfaECall
ModuleTwoFactorTotpModuleMfaTotp
ModuleTwoFactorPasskeyModuleMfaPasskey
ModuleTwoFactorEmailModuleMfaEmail
ModuleTwoFactorIWayModuleMfaIWay
ModuleTwoFactorSwisscomModuleMfaSwisscom
ProAuth.TwoFactorModules.*ProAuth.MfaModules.*
ProAuthTwoFactorModuleProAuthMfaModule

For example:

json
{
  "Modules": {
    "ModuleMfaTotp": "ProAuth.MfaModules.Totp.Modularity.ProAuthMfaModule, ProAuth.MfaModules.Totp",
    "ModuleMfaTwilio": "ProAuth.MfaModules.Twilio.Modularity.ProAuthMfaModule, ProAuth.MfaModules.Twilio",
    "ModuleMfaECall": "ProAuth.MfaModules.eCall.Modularity.ProAuthMfaModule, ProAuth.MfaModules.eCall",
    "ModuleMfaPasskey": "ProAuth.MfaModules.Passkey.Modularity.ProAuthMfaModule, ProAuth.MfaModules.Passkey"
  }
}

ProAuth 3.x accepts legacy MFA module aliases during startup to ease upgrades, but configuration should be updated to the new names so future exports, diagnostics, and support guidance match the product terminology.

Option Keys

Update customer-maintained option import files and scripts that use old MFA-related option names:

Legacy option keyProAuth 3.x option key
SecondFactorResetEnabledMfaFactorResetEnabled
SecondFactorPlaceholderHintMfaFactorPlaceholderHint
SecondFactorMailBodyTemplateMfaFactorMailBodyTemplate
SecondFactorMailSubjectTemplateMfaFactorMailSubjectTemplate
SecondFactorMailBodyTemplateViewMfaFactorMailSubjectTemplateView
SecondFactorTemporaryLockEnabledMfaTemporaryLockEnabled
SecondFactorTemporaryLockThresholdMfaTemporaryLockThreshold
SecondFactorTemporaryLockDurationSecondsMfaTemporaryLockDurationSeconds
SecondFactorThrottlingEnabledMfaThrottlingEnabled
SecondFactorThrottlingBaseDelayMsMfaThrottlingBaseDelayMs
SecondFactorThrottlingMaxDelayMsMfaThrottlingMaxDelayMs
SecondFactorMaxCodeResendsMfaMaxCodeResends
SecondFactorCodeResendCooldownSecondsMfaCodeResendCooldownSeconds
SecondFactorCaptchaEnabledMfaCaptchaEnabled
SecondFactorCaptchaActivationModeMfaCaptchaActivationMode
SecondFactorCaptchaFailureThresholdMfaCaptchaFailureThreshold
SecondFactorCaptchaProviderMfaCaptchaProvider
SecondFactorCaptchaSiteKeyMfaCaptchaSiteKey
SecondFactorCaptchaSecretKeyMfaCaptchaSecretKey

The tenant recovery-code option keys Create2FaRecoveryCodes and Show2FaRecoveryCodes are retained as compatibility keys in ProAuth 3.x. Existing values do not need to be renamed during the v2-to-v3 upgrade.

Import and Export Files

For ProAuth 3.x CLI data import files, use the new API-versioned YAML schema and the Mfa* resource and property names. Legacy v1 import files that still contain TwoFactor*, SecondFactor*, twoFactorInstances, or treatSecondFactorClaimsAsConfirmed must be migrated with proauthcli data migrate-yaml before they are imported with ProAuth 3.x.

The migration command maps legacy TwoFactorInstanceDto resources to kind: MfaInstance and moves legacy navigation collections into explicit relationships blocks.

User Store API Migration

The User Store API v2 follows the same CQRS-lite pattern. All endpoints include the instanceId path parameter.

Base Path Change

v1v2
/api/userstore/v1/{instanceId}/{resource}/api/userstore/v2/{instanceId}/{resource}

DTO Naming Convention Changes

The same naming convention applies:

Operationv1 DTOv2 DTO
Read (GET response)UserDtoUser
Create (POST body)UserDtoCreateUserCommand
Update (PUT body)UserDtoUpdateUserCommand
Patch (PATCH body)(not available)PatchUserCommand

Client Library Migration

xml
<!-- v1 (deprecated) -->
<PackageReference Include="ProAuth.Clients.UserStore" Version="..." />

<!-- v2 (recommended) -->
<PackageReference Include="ProAuth.Clients.UserStore.V2" Version="..." />

Database Migration

Automatic: Legacy View Archival

The ProAuth 3.x database deployment automatically archives existing .cshtml view customizations. Your old Razor views are not deleted — they are moved to a _legacy/ path prefix so you can reference them while rebuilding your customizations in Fluid.

What happens during upgrade:

Before (2.x)After (3.x)
/Views/tenant/{id}/Authentication/Login.cshtml/Views/_legacy/tenant/{id}/Authentication/Login.cshtml
/Views/app/{id}/Shared/_Layout.cshtml/Views/_legacy/app/{id}/Shared/_Layout.cshtml

Archived views remain accessible through the Management API and Admin UI. They will not be rendered by the Fluid view engine. Delete them via the API once your migration is complete.

INFO

Only .cshtml view entries are archived. Custom assets (CSS, images, fonts) stored in the CustomAsset table are not affected and continue to work.

Manual: Clean Up Legacy Views

After migrating your customizations to Fluid templates, remove the archived legacy views:

http
# v2 (recommended)
DELETE /api/management/v2/customview/{encodedLocation}

# v1 (deprecated, still functional)
DELETE /api/management/v1/customview/{encodedLocation}

Or use the Admin UI to browse and delete views under the _legacy/ prefix.

View Customization Migration

Template Syntax

All custom views must be rewritten from Razor to Fluid syntax. The following table maps common Razor patterns to their Fluid equivalents:

Razor (2.x)Fluid (3.x)
@model LoginViewModel(not needed — model is automatic)
@{ Layout = "_Layout"; }{% layout '_Layout' %}
@Html.AntiForgeryToken()Built into {% auth_form %}
<form asp-controller="UserStore" asp-action="Login">{% auth_form controller: 'userstore', action: 'login' %}
<input asp-for="LoginName" />{% text_input for: 'LoginName', label_key: 'my-label' %}
<input asp-for="Password" type="password" />{% password_input for: 'Password', label_key: 'my-label' %}
<span asp-validation-for="...">{% validation_summary %}
@(await labelService.GetLabel("key")){% auth_title key: 'my-key', tag: 'span' %}
@if (Model.HasError) { ... }{% if model.HasError %} ... {% endif %}
@foreach (var x in Model.Items) { ... }{% for x in model.Items %} ... {% endfor %}
@Model.TenantId
@RenderBody(){% renderbody %}
@RenderSection("scripts"){% rendersection 'scripts' %}
@section scripts { ... }{% section 'scripts' %} ... {% endsection %}

Hidden Fields → Auth Flow Session

In 2.x, form state was rendered as individual <input type="hidden"> fields, exposing values like return URLs, tenant IDs, and authentication schemes in the HTML source.

In 3.x, all flow state is stored server-side. Templates emit a single opaque identifier:

liquid
{% auth_flow_id %}

This renders as <input type="hidden" name="__flowId" value="..." /> — the actual state remains on the server.

Action required: Remove any @Html.HiddenFor(...) calls for flow state properties. Replace them with {% auth_flow_id %} inside your {% auth_form %} block.

Building Block Tags

ProAuth 3.x provides building block tags that encapsulate security-critical rendering logic. Use these instead of raw HTML. Tags use named parameters with the syntax parameter: value.

TagPurpose
{% auth_form controller: '...', action: '...' %}...{% endauth_form %}Form with anti-forgery token
{% auth_flow_id %}Auth flow session hidden field
{% text_input for: 'Field', label_key: '...' %}Labeled text input
{% password_input for: 'Field', label_key: '...' %}Password input with show/hide toggle
{% checkbox for: 'Field', label_key: '...' %}Checkbox input
{% submit_button key: '...' %}Submit button with loading spinner
{% auth_title key: '...', tag: 'h1' %}Localized heading / text
{% validation_summary %}Validation error list
{% error_info_block %}Error and info message display
{% idp_button_list %}External IdP login buttons
{% language_selector %}Language switcher dropdown
{% captcha_widget %}Captcha widget
{% captcha_scripts %}Captcha provider JavaScript
{% link key: '...', href: '...' %}Localized link
{% qr_code 'PropertyName' %}QR code image
{% alert type: 'warning', key: '...' %}Alert / notification message
{% icon name: '...' %}Inline SVG icon
{% password_strength for: 'Field' %}Password strength meter
{% include '_PartialName' %}Include a partial template

For the full building block reference with all parameters, see Auth View Customization.

Migration Example

2.x Login View (Razor):

cshtml
@model ProAuth.IdpModules.UserStore.Common.ViewModels.LoginViewModel
@inject ILabelService labelService
@{
    Layout = "_Layout";
}
<h2>@(await labelService.GetLabel("userlogin-title"))</h2>
<form asp-controller="UserStore" asp-action="Login" method="post">
    @Html.AntiForgeryToken()
    @foreach (var prop in ViewData.ModelMetadata.Properties
        .Where(p => p.AdditionalValues.ContainsKey("HiddenField")))
    {
        <input type="hidden" name="@prop.PropertyName"
               value="@prop.PropertyGetter(Model)" />
    }
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="LoginName">
            @(await labelService.GetLabel("userlogin-username-input-placeholder"))
        </label>
        <input asp-for="LoginName" class="form-control" autofocus />
    </div>
    <div class="form-group">
        <label asp-for="Password">
            @(await labelService.GetLabel("userlogin-password-input-placeholder"))
        </label>
        <input asp-for="Password" type="password" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">
        @(await labelService.GetLabel("userlogin-sign-in"))
    </button>
</form>

3.x Login View (Fluid):

liquid
{% layout '_Layout' %}

{% section 'scripts' %}
    {% validation_scripts %}
{% endsection %}

<div class="proauth-view-header">
    {% auth_title key: 'userlogin-title', variables: model.InstanceName,
                  tag: 'h1', class: 'proauth-view-title' %}
</div>

{% auth_form controller: 'userstore', action: 'login', method: 'post' %}
    {% auth_flow_id %}

    {% text_input for: 'LoginName',
                  label_key: 'userlogin-username-input-placeholder',
                  autofocus: true, autocomplete: 'username', required: true %}

    {% password_input for: 'Password',
                      label_key: 'userlogin-password-input-placeholder',
                      show_toggle: true, autocomplete: 'current-password',
                      required: true %}

    {% validation_summary %}
    {% error_info_block %}

    {% submit_button key: 'userlogin-sign-in' %}
{% endauth_form %}

Key differences:

  • No @model directive — the model is automatically available as model
  • No hidden field loop{% auth_flow_id %} replaces all hidden flow state fields
  • No @inject — labels are resolved by the tag via the key: parameter
  • No anti-forgery token — automatically injected by {% auth_form %}
  • Named parametersfor:, label_key:, key: instead of positional arguments

MFA Path Rename

The authentication view controller prefix remains /Mfa, but the individual MFA view routes were renamed:

2.x Path3.x Path
/Mfa/MfaFactorCode/Mfa/Verification
/Mfa/MfaFactorRegistration/Mfa/Setup
/Mfa/MfaFactorConfirmation/Mfa/Confirmation
/Mfa/MfaFactorRecoveryCodes/Mfa/RecoveryCodes
/Mfa/RecoveryCode/Mfa/RecoveryVerification

Action required:

  • Update bookmarks, documentation, monitoring checks, reverse-proxy rules, and deep links that reference the old MFA view routes
  • Recreate custom view overrides at the new MFA paths using Fluid syntax

CSS Theming Migration

ProAuth 3.x replaces Bootstrap with Tailwind CSS and uses CSS custom properties for theming.

Bootstrap → Tailwind

Bootstrap utility classes (e.g., btn btn-primary, form-control, text-danger) no longer work. ProAuth provides its own component classes:

Bootstrap (2.x)ProAuth (3.x)
btn btn-primaryproauth-btn proauth-btn-primary
form-controlproauth-input
text-dangerproauth-alert proauth-alert-error
cardproauth-form-container
alert alert-warningproauth-alert proauth-alert-warning

CSS Custom Properties

Override these CSS custom properties to apply your branding. Upload a custom CSS file via Custom Assets and reference it in a custom _Head.liquid partial:

css
:root {
    /* Brand colors */
    --proauth-primary: #0041c8;
    --proauth-primary-hover: #0036a8;
    --proauth-primary-light: #eef3ff;
    --proauth-accent: #0041c8;

    /* Background and text */
    --proauth-bg: #f8f9fc;
    --proauth-card-bg: #ffffff;
    --proauth-text: #0f172a;
    --proauth-text-muted: #64748b;

    /* Form inputs */
    --proauth-input-border: #d1d5db;
    --proauth-input-focus: #0041c8;

    /* Status colors */
    --proauth-error: #dc2626;
    --proauth-success: #16a34a;
    --proauth-warning: #d97706;
    --proauth-info: #2563eb;

    /* Typography */
    --proauth-font-family: 'Plus Jakarta Sans', sans-serif;

    /* Border radius */
    --proauth-input-radius: 0.5rem;
    --proauth-button-radius: 0.5rem;

    /* Brand panel gradient */
    --proauth-brand-gradient-from: #0041c8;
    --proauth-brand-gradient-to: #001a57;

    /* Logo images (CSS background-image) */
    --proauth-logo-url: url('/images/logo.svg');
    --proauth-logo-sm-url: url('/images/logo-name.svg');
}

TIP

For simple branding changes (logo, colors, fonts), overriding CSS custom properties via a custom CSS asset and a custom _Head.liquid is often sufficient — no view template changes needed. See Customization Scenarios for step-by-step examples.

View Location Paths

The view location hierarchy is unchanged. Only the file extension changed from .cshtml to .liquid:

LevelPath Pattern
Tenant/Views/tenant/{tenantId}/{Controller}/{ViewName}.liquid
Client App/Views/app/{clientAppId}/{Controller}/{ViewName}.liquid
Subscription/Views/subscription/{subscriptionId}/{Controller}/{ViewName}.liquid
Default/Views/{Controller}/{ViewName}.liquid

Priority order (highest first): Tenant → Client App → Subscription → Default

Template Validation

The Management API validates Fluid template syntax when creating or updating custom views. Invalid templates are rejected with a 400 Bad Request response containing parse error details. This prevents deploying broken templates to production.

Security Improvements Summary

Aspect2.x3.x
Template code executionArbitrary C# via RazorNo code execution (Fluid sandbox)
Form state exposureAll hidden fields visible in HTML sourceSingle opaque __flowId, state on server
Template limitsNone10,000 steps, recursion depth 10
Template validationNoneSyntax validated on save
Anti-forgery tokensManual @Html.AntiForgeryToken()Automatic via {% auth_form %}