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
proauthanduserstoreschemas; - moves existing tables from
dboto 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
/labeland/pageroots to the ProAuth 3.x scoped roots; - drops the legacy
_MigrationScriptsHistorytable after a successful migration; - creates the schema-local EF Core migration history table and stamps the
InitialV3migration 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:
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:
appsettings:
data:
defaultconnection:
provider: SqlServer
connectionstring: VALUE_TO_OVERRIDE
commandtimeoutinseconds: 30The 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:
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 secret | ProAuth 3.x external secret | Notes |
|---|---|---|
userstoreconnectionsaliases | runtime | Alias keys move to Data__UserStoreConnections__ConnectionStringAliases__<alias> |
datadefaultconnection | runtime | Only the connection string remains secret |
license | runtime | License data remains secret |
baseservicesettings | runtime or non-secret config | Only mailserverconfig remains in Secret as BaseServiceSettings__MailServerConfig |
proauthroot | crypto | Includes client app secret, SCIM token key, and default signing certificate |
encryptionkeys | crypto | Certificate material and passwords remain secret; mode is non-secret |
dbschemadeployment | dbSchemaDeployment | Keys are renamed to direct env-var names |
dbdeploymentworkerSqlserver | dbWorkerSqlserver | Credentials remain secret; containment is non-secret |
dbdeploymentworkerAzuresql | dbWorkerAzuresql | Credentials remain secret; Azure resource identifiers and auth mode are non-secret |
dbdeploymentworkerPostgresql | dbWorkerPostgresql | Credentials remain secret; admindatabase is non-secret |
ProAuth Core Key Mapping
| ProAuth 2.x secret/key | ProAuth 3.x secret/key |
|---|---|
datadefaultconnection.connectionstring | runtime.Data__DefaultConnection__ConnectionString |
datadefaultconnection.provider | Helm value or appsettings.overrides.json: Data:DefaultConnection:Provider |
datadefaultconnection.commandtimeoutinseconds | Helm value or appsettings.overrides.json: Data:DefaultConnection:CommandTimeoutInSeconds |
userstoreconnectionsaliases.<alias> | runtime.Data__UserStoreConnections__ConnectionStringAliases__<alias> |
license.licensedata | runtime.License__LicenseData |
baseservicesettings.mailserverconfig | runtime.BaseServiceSettings__MailServerConfig |
baseservicesettings.hosturl | Helm value or appsettings.overrides.json: BaseServiceSettings:HostUrl |
baseservicesettings.emailsenderaddress | Helm value or appsettings.overrides.json: BaseServiceSettings:EmailSenderAddress |
baseservicesettings.requirehttpsmetadata | Helm value or appsettings.overrides.json: BaseServiceSettings:RequireHttpsMetadata |
baseservicesettings.sessionidletimeout | Helm value or appsettings.overrides.json: BaseServiceSettings:SessionIdleTimeout |
baseservicesettings.jobqueueinterval | Helm value or appsettings.overrides.json: BaseServiceSettings:JobQueueInterval |
baseservicesettings.forwardlimit | Helm value or appsettings.overrides.json: BaseServiceSettings:ForwardLimit |
baseservicesettings.knownproxies | Helm value or appsettings.overrides.json: BaseServiceSettings:KnownProxies |
baseservicesettings.knownnetworks | Helm value or appsettings.overrides.json: BaseServiceSettings:KnownNetworks |
proauthroot.clientappsecret | crypto.ProAuthRoot__ClientAppSecret |
proauthroot.scimtokensecuritykey | crypto.ProAuthRoot__ScimTokenSecurityKey |
proauthroot.defaultcertvalue | crypto.ProAuthRoot__DefaultCertValue |
proauthroot.defaultcertpassword | crypto.ProAuthRoot__DefaultCertPassword |
encryptionkeys.certificate | crypto.EncryptionKeys__Certificate |
encryptionkeys.certificatepassword | crypto.EncryptionKeys__CertificatePassword |
encryptionkeys.mode | Helm value or appsettings.overrides.json: EncryptionKeys:Mode |
encryptionkeys.KeyRotationDecryptionCertificates__<index>__Certificate | crypto.EncryptionKeys__KeyRotationDecryptionCertificates__<index>__Certificate |
encryptionkeys.KeyRotationDecryptionCertificates__<index>__CertificatePassword | crypto.EncryptionKeys__KeyRotationDecryptionCertificates__<index>__CertificatePassword |
dbschemadeployment.user | dbSchemaDeployment.ProAuthDbSchemaDeployment__User |
dbschemadeployment.password | dbSchemaDeployment.ProAuthDbSchemaDeployment__Password |
dbdeploymentworkerSqlserver.connectionstring | dbWorkerSqlserver.SqlServer__ConnectionString |
dbdeploymentworkerSqlserver.dbuser | dbWorkerSqlserver.SqlServer__DbUser |
dbdeploymentworkerSqlserver.dbpassword | dbWorkerSqlserver.SqlServer__DbPassword |
dbdeploymentworkerSqlserver.deploymentuser | dbWorkerSqlserver.SqlServer__DeploymentUser |
dbdeploymentworkerSqlserver.deploymentpassword | dbWorkerSqlserver.SqlServer__DeploymentPassword |
dbdeploymentworkerSqlserver.containment | Helm value or appsettings.overrides.json: SqlServer:Containment |
dbdeploymentworkerAzuresql.clientsecret | dbWorkerAzuresql.AzureSql__ClientSecret |
dbdeploymentworkerAzuresql.sqladminuser | dbWorkerAzuresql.AzureSql__SqlAdminUser |
dbdeploymentworkerAzuresql.sqladminpassword | dbWorkerAzuresql.AzureSql__SqlAdminPassword |
dbdeploymentworkerAzuresql.dbuser | dbWorkerAzuresql.AzureSql__DbUser |
dbdeploymentworkerAzuresql.dbpassword | dbWorkerAzuresql.AzureSql__DbPassword |
dbdeploymentworkerAzuresql.deploymentuser | dbWorkerAzuresql.AzureSql__DeploymentUser |
dbdeploymentworkerAzuresql.deploymentpassword | dbWorkerAzuresql.AzureSql__DeploymentPassword |
dbdeploymentworkerPostgresql.connectionstring | dbWorkerPostgresql.PostgreSql__ConnectionString |
dbdeploymentworkerPostgresql.admindatabase | Helm value or appsettings.overrides.json: PostgreSql:AdminDatabase |
dbdeploymentworkerPostgresql.dbuser | dbWorkerPostgresql.PostgreSql__DbUser |
dbdeploymentworkerPostgresql.dbpassword | dbWorkerPostgresql.PostgreSql__DbPassword |
dbdeploymentworkerPostgresql.deploymentuser | dbWorkerPostgresql.PostgreSql__DeploymentUser |
dbdeploymentworkerPostgresql.deploymentpassword | dbWorkerPostgresql.PostgreSql__DeploymentPassword |
ProAuth Admin App Secret Groups
| ProAuth Admin App 2.x external secret | ProAuth Admin App 3.x external secret | Notes |
|---|---|---|
authentication | runtime | Only confidential authentication values remain secret |
initializersettings | resourceDeployment | Only the resource deployment client secret remains secret |
encryptionkeys | crypto | Certificate material and passwords remain secret; mode is non-secret |
baseservicesettings | non-secret config | Move to Helm values or appsettings.overrides.json |
ProAuth Admin App Key Mapping
| ProAuth Admin App 2.x secret/key | ProAuth Admin App 3.x secret/key |
|---|---|
authentication.clientsecret | runtime.AuthenticationSettings__ClientSecret |
authentication.dpop__jsonwebkey | runtime.AuthenticationSettings__Dpop__JsonWebKey |
authentication.clientassertion__certificate | runtime.AuthenticationSettings__ClientAssertion__Certificate |
authentication.clientassertion__certificatepassword | runtime.AuthenticationSettings__ClientAssertion__CertificatePassword |
authentication.mutualtls__certificate | runtime.AuthenticationSettings__MutualTls__Certificate |
authentication.mutualtls__certificatepassword | runtime.AuthenticationSettings__MutualTls__CertificatePassword |
authentication.requestobject__certificate | runtime.AuthenticationSettings__RequestObject__Certificate |
authentication.requestobject__certificatepassword | runtime.AuthenticationSettings__RequestObject__CertificatePassword |
authentication.authority | Helm value or appsettings.overrides.json: AuthenticationSettings:Authority |
authentication.clientid | Helm value or appsettings.overrides.json: AuthenticationSettings:ClientId |
authentication.tenantid | Helm value or appsettings.overrides.json: AuthenticationSettings:TenantId |
initializersettings.resourceclientsecret | resourceDeployment.InitializerSettings__ResourceClientSecret |
initializersettings.serviceurl | Helm value or appsettings.overrides.json: InitializerSettings:ServiceUrl |
initializersettings.resourceclientid | Helm value or appsettings.overrides.json: InitializerSettings:ResourceClientId |
initializersettings.resourcetenantid | Helm value or appsettings.overrides.json: InitializerSettings:ResourceTenantId |
encryptionkeys.certificate | crypto.EncryptionKeys__Certificate |
encryptionkeys.certificatepassword | crypto.EncryptionKeys__CertificatePassword |
encryptionkeys.mode | Helm value or appsettings.overrides.json: EncryptionKeys:Mode |
baseservicesettings.* | Helm values or appsettings.overrides.json under BaseServiceSettings |
Example v3 External Secret Values
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__CertificatePasswordFor non-secret pre-created settings:
externalConfig:
enabled: true
existingConfigMap: proauth-overrides
key: appsettings.overrides.json
mountPath: /appHelm 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:
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/componentUpdate 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:
# 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-appProAuth Core Resource Names
| ProAuth 2.x resource | ProAuth 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 name | proauth-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 resource | ProAuth 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 container | proauthadminapp-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.
| Area | 2.x | 3.x |
|---|---|---|
| Management & User Store APIs | v1 only | v1 (deprecated) + v2 (recommended) |
| API pattern | Traditional CRUD DTOs | CQRS-lite with typed commands |
| .NET API identifiers | Generic Guid values | Domain-specific typed IDs in v2 clients |
| Permission model | Flag-based ProAuthSecurityRole enum | Role-based access control (RBAC) with scoped permissions |
| Client libraries | ProAuth.Clients.Management / .UserStore | v2 packages: ProAuth.Clients.Management.V2 / .UserStore.V2 |
| CLI YAML imports | YAML tags and v1 DTO shapes, for example !CustomerDto | API-versioned resources with apiVersion, kind, metadata, spec, and relationships |
| Template engine | Razor (.cshtml) | Fluid/Liquid (.liquid) |
| CSS framework | Bootstrap + jQuery | Tailwind CSS + vanilla JS |
| Form state | Hidden form fields in HTML | Server-side auth flow session |
| MFA terminology | TwoFactor / SecondFactor names | Mfa / MfaFactor names |
| Code execution | Arbitrary C# in templates | Building block tags only |
| Template validation | None | Syntax 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/SecondFactornames to the newMfa/MfaFactornames. - [ ] 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
certificateInputmodel and Kubernetes mounted Secret files for PEM certificate/private-key pairs. - [ ] Client application public keys — ProAuth v3 uses
ClientAppKeySetrecords forprivate_key_jwt, JAR request-object signing,self_signed_tls_client_auth, and token encryption keys. Configuration repositories and automation must createclientAppKeySetsin YAML or call the v2ClientAppKeySetAPI; public keys are no longer configured onClientApporClientAppAuthenticationConfiguration.
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:
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:
# 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" --overwriteFor data imports, keep the default conflict strategy during the first migration:
proauthcli data migrate-yaml "./config/*.yml" --outputPath "./config-v3" --conflictStrategy failThis 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 YAML | ProAuth 3.x YAML |
|---|---|
Resource type is a YAML tag such as !ClientAppDto | Resource type is apiVersion + kind |
| Entity fields are placed directly at document root | Entity fields are placed under spec |
| IDs are normal DTO properties | Entity ID is metadata.id; User Store resources also use metadata.userStoreId |
| Navigation properties may be nested on either side | Relationships are declared explicitly under relationships |
| Relationship intent is implicit | Relationship intent is mode: set, add, or remove |
Legacy TwoFactor* and SecondFactor* names may appear | New 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/key | ProAuth 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:
relationships:
memberUsers:
mode: set
refs:
- "{{proauthuserfromuserstore:e8921401-04d5-45d9-80c9-0dce5c4a72bd.6f00f971-1a5c-4f6d-92da-855833f4da9f}}"The meaning is deterministic:
| Declaration | Result |
|---|---|
| Relationship omitted | The CLI does not manage that relationship. Existing database values remain unchanged. |
mode: set with refs | The CLI replaces the relationship set with exactly those refs. |
mode: set with refs: [] | The CLI clears the relationship. |
mode: add | The CLI adds the refs and keeps existing values. |
mode: remove | The 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
| v1 | v2 |
|---|---|
/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:
| Operation | v1 DTO | v2 DTO |
|---|---|---|
| Read (GET response) | ClientAppDto | ClientApp |
| Create (POST body) | ClientAppDto | CreateClientAppCommand |
| Update (PUT body) | ClientAppDto | UpdateClientAppCommand |
| 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:
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:
# 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}/scopeNew: RBAC Permission Model
v2 introduces a proper Role-Based Access Control (RBAC) model replacing the flag-based ProAuthSecurityRole enum:
| v1 Concept | v2 Equivalent |
|---|---|
ProAuthSecurityRole enum flags | ProAuthRole with scoped ProAuthPermission definitions |
ProAuthUserRole table | ProAuthRoleAssignment with PrincipalType.User |
ProAuthGroupRole table | ProAuthRoleAssignment with PrincipalType.Group |
ClientAppRole table | ProAuthRoleAssignment 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 scopeNew 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:
<!-- v1 (deprecated) -->
<PackageReference Include="ProAuth.Clients.Management" Version="..." />
<!-- v2 (recommended) -->
<PackageReference Include="ProAuth.Clients.Management.V2" Version="..." />Update your using directives:
// 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:
POST /api/management/v1/clientapp
Content-Type: application/json
{
"name": "my-app",
"displayName": "My Application",
"clientAppType": "Confidential",
"accessGrantConsentType": "Implicit"
}v2:
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:
{
"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:
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 name | 3.x / v2 name |
|---|---|
TwoFactor | Mfa |
TwoFactorInstance | MfaInstance |
TwoFactorType | MfaType |
TwoFactorInstanceMetadata | MfaInstanceMetadata |
TwoFactorRecoveryCode | MfaRecoveryCode |
ProAuthUserSecondFactor | ProAuthUserMfaFactor |
SecondFactorType | MfaFactorType |
SecondFactorValue | MfaFactorValue |
SecondFactorPasskeyCredential | MfaFactorPasskeyCredential |
Management API v2 Routes and Payloads
Update custom v2 integrations and scripts to the new resource names:
| Legacy v2 name | ProAuth 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}/twofactorinstances | ClientApp/{id}/mfainstances |
Tenant/{id}/twofactorinstances | Tenant/{id}/mfainstances |
IdpInstance/{id}/twofactorinstances | IdpInstance/{id}/mfainstances |
Common JSON property changes:
| Legacy property | ProAuth 3.x property |
|---|---|
twoFactorInstances | mfaInstances |
twoFactorTypeId | mfaTypeId |
applyToTwoFactorInstanceId | applyToMfaInstanceId |
secondFactorType | mfaFactorType |
secondFactorValue | mfaFactorValue |
treatSecondFactorClaimsAsConfirmed | treatMfaFactorClaimsAsConfirmed |
Module Configuration
Update module registrations in appsettings.json, Helm values, Kubernetes configuration, and any environment-specific overrides:
| 2.x module registration | 3.x module registration |
|---|---|
ModuleTwoFactorTwilio | ModuleMfaTwilio |
ModuleTwoFactorECall | ModuleMfaECall |
ModuleTwoFactorTotp | ModuleMfaTotp |
ModuleTwoFactorPasskey | ModuleMfaPasskey |
ModuleTwoFactorEmail | ModuleMfaEmail |
ModuleTwoFactorIWay | ModuleMfaIWay |
ModuleTwoFactorSwisscom | ModuleMfaSwisscom |
ProAuth.TwoFactorModules.* | ProAuth.MfaModules.* |
ProAuthTwoFactorModule | ProAuthMfaModule |
For example:
{
"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 key | ProAuth 3.x option key |
|---|---|
SecondFactorResetEnabled | MfaFactorResetEnabled |
SecondFactorPlaceholderHint | MfaFactorPlaceholderHint |
SecondFactorMailBodyTemplate | MfaFactorMailBodyTemplate |
SecondFactorMailSubjectTemplate | MfaFactorMailSubjectTemplate |
SecondFactorMailBodyTemplateView | MfaFactorMailSubjectTemplateView |
SecondFactorTemporaryLockEnabled | MfaTemporaryLockEnabled |
SecondFactorTemporaryLockThreshold | MfaTemporaryLockThreshold |
SecondFactorTemporaryLockDurationSeconds | MfaTemporaryLockDurationSeconds |
SecondFactorThrottlingEnabled | MfaThrottlingEnabled |
SecondFactorThrottlingBaseDelayMs | MfaThrottlingBaseDelayMs |
SecondFactorThrottlingMaxDelayMs | MfaThrottlingMaxDelayMs |
SecondFactorMaxCodeResends | MfaMaxCodeResends |
SecondFactorCodeResendCooldownSeconds | MfaCodeResendCooldownSeconds |
SecondFactorCaptchaEnabled | MfaCaptchaEnabled |
SecondFactorCaptchaActivationMode | MfaCaptchaActivationMode |
SecondFactorCaptchaFailureThreshold | MfaCaptchaFailureThreshold |
SecondFactorCaptchaProvider | MfaCaptchaProvider |
SecondFactorCaptchaSiteKey | MfaCaptchaSiteKey |
SecondFactorCaptchaSecretKey | MfaCaptchaSecretKey |
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
| v1 | v2 |
|---|---|
/api/userstore/v1/{instanceId}/{resource} | /api/userstore/v2/{instanceId}/{resource} |
DTO Naming Convention Changes
The same naming convention applies:
| Operation | v1 DTO | v2 DTO |
|---|---|---|
| Read (GET response) | UserDto | User |
| Create (POST body) | UserDto | CreateUserCommand |
| Update (PUT body) | UserDto | UpdateUserCommand |
| Patch (PATCH body) | (not available) | PatchUserCommand |
Client Library Migration
<!-- 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:
# 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:
{% 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.
| Tag | Purpose |
|---|---|
{% 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):
@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):
{% 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
@modeldirective — the model is automatically available asmodel - No hidden field loop —
{% auth_flow_id %}replaces all hidden flow state fields - No
@inject— labels are resolved by the tag via thekey:parameter - No anti-forgery token — automatically injected by
{% auth_form %} - Named parameters —
for:,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 Path | 3.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-primary | proauth-btn proauth-btn-primary |
form-control | proauth-input |
text-danger | proauth-alert proauth-alert-error |
card | proauth-form-container |
alert alert-warning | proauth-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:
: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:
| Level | Path 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
| Aspect | 2.x | 3.x |
|---|---|---|
| Template code execution | Arbitrary C# via Razor | No code execution (Fluid sandbox) |
| Form state exposure | All hidden fields visible in HTML source | Single opaque __flowId, state on server |
| Template limits | None | 10,000 steps, recursion depth 10 |
| Template validation | None | Syntax validated on save |
| Anti-forgery tokens | Manual @Html.AntiForgeryToken() | Automatic via {% auth_form %} |