- .NET 10 SDK
- Azure Functions Core Tools v4 (
npm i -g azure-functions-core-tools@4) for local Function debugging - Azure CLI (
az) with the Bicep extension (az bicep install) - A SQL Server instance for local dev (LocalDB on Windows, or Azure SQL / SQL Server container elsewhere)
- The three Entra ID objects from entra-app-setup.md
- Paycom API access - see paycom-integration.md.
Get sandbox credentials from Paycom's automation team first
(
automation@paycomonline.com) and allow-list your dev machine's IP before expecting localGetAllEmployeesAsynccalls to succeed (a 401 from Paycom almost always means the calling IP isn't allow-listed).
IdentityFlow.Functions.csprojrequireslocal.settings.jsonto exist just to build (Functions tooling copies it to the output directory) - copy the template and fill in your own values:Do the same conceptually forcp src/IdentityFlow.Functions/local.settings.json.example src/IdentityFlow.Functions/local.settings.jsonsrc/IdentityFlow.Web/appsettings.Development.json(no template checked in for it since ASP.NET Core doesn't require the file to exist - only add one if you want to overrideappsettings.jsonlocally). Both need the same configuration keys -Paycom:*,Entra:*,Sql:ConnectionString, and (Web only)AzureAd:*. Never commit real secrets to either file; both are gitignored.- Apply the initial EF Core migration to your local database:
(Both hosts also call
dotnet tool install --global dotnet-ef dotnet ef database update --project src/IdentityFlow.Data --startup-project src/IdentityFlow.DataDatabase.Migrate()on startup, so this is mostly useful for inspecting the schema ahead of time.) - Run the Function host:
cd src/IdentityFlow.Functions && func start - Run the Web app:
cd src/IdentityFlow.Web && dotnet run - Add at least one
FieldMappingwithIsMatchingAttribute = true(typicallyuserPrincipalName) before running a sync, or the Entra provisioning job has no way to anchor incoming records to existing users. - Use the dashboard's Run Sync Now with Dry run checked first, and
review
SyncRuns/Detailsbefore ever running it for real.
This template provisions Azure SQL Database (single database),
GeneralPurpose tier, Serverless compute only - sqlDatabaseSkuName
(default GP_S_Gen5_1) only picks the vCore size within that one tier.
Azure SQL Managed Instance, SQL Server on a VM, Elastic Pools, Hyperscale,
Business Critical, any DTU tier (Basic/Standard/Premium), and even
GeneralPurpose Provisioned (non-serverless) compute are not supported by
infra/modules/sql.bicep as written - it hardcodes tier: 'GeneralPurpose'
and the serverless-only autoPauseDelay/minCapacity properties, so
passing an incompatible SKU name fails the deployment. GeneralPurpose
Serverless auto-pauses when idle and bills accordingly, which fits this
workload's bursty, infrequent-sync-runs usage pattern - if you need a
different tier (e.g. Business Critical for higher availability SLAs),
sql.bicep needs updating to match, not just the parameter.
az group create -n rg-paycom-provisioner -l eastus2
az deployment group create \
-g rg-paycom-provisioner \
-f infra/main.bicep \
-p infra/main.bicepparam
After the deployment finishes:
- Populate the Key Vault secrets it created placeholders for (
Paycom-*,Entra-*,AzureAd-*- see the@Microsoft.KeyVault(...)references ininfra/modules/functionApp.bicepandinfra/modules/webApp.bicepfor the exact secret names expected):az keyvault secret set --vault-name <kv-name> --name Paycom-BaseUrl --value "https://..." az keyvault secret set --vault-name <kv-name> --name Entra-ClientSecret --value "..." # ...and so on for every @Microsoft.KeyVault(...) reference in the two app modules. - Run
infra/post-deploy-sql-grants.sqlagainst the new database as the Entra AAD admin configured inmain.bicepparam, substituting the actual Function App / Web App names from the deployment output. This grantsdb_ddladminto both managed identities specifically so the next step can work: neither Bicep/ARM nor this script create the actual application schema (tables, indexes, migrations history) - both hosts callDatabase.Migrate()on startup (see Local development above), so the schema gets created automatically the first time either app starts against the new database. No separate manual migration step against Azure SQL is needed, but the first startup after a fresh deployment will take a little longer than subsequent ones while it applies every migration from scratch. - Register the Function App's outbound IPs with Paycom before flipping on
the live (non-sandbox) credentials - Paycom only accepts calls from
allow-listed IPs:
Send that list to your Paycom Specialist/automation team. See paycom-integration.md for the VNet+NAT Gateway alternative if you'd rather have one static egress IP than a range.
az functionapp show -g rg-paycom-provisioner -n <functionAppName> --query possibleOutboundIpAddresses -o tsv - Deploy the application code. Either build fresh from source:
or, for a specific tagged version, download the pre-built artifacts the release workflow attaches to every published GitHub Release (
dotnet publish src/IdentityFlow.Functions -c Release -o out/functions func azure functionapp publish <functionAppName> --dotnet-isolated dotnet publish src/IdentityFlow.Web -c Release -o out/web az webapp deploy -g rg-paycom-provisioner -n <webAppName> --src-path out/webIdentityFlow.Functions-<tag>.zip/IdentityFlow.Web-<tag>.zip), unzip each, and run the samefunc azure functionapp publish/az webapp deploycommands above against the unzippedout/functions/out/webfolders instead of a fresh local build - useful for deploying an exact, already-tested build rather than whatever happens to be checked out locally. That workflow is artifact-only today (it doesn't deploy anywhere itself) since there's no Azure target/credentials wired into CI yet - see its own comments for what a future auto-deploy-on-release step would still need. - Sign in to the Web app once as an admin and add your real field mappings and group rules (or seed them via a database script if you're migrating from an existing process).
- Run a dry-run sync from the dashboard, review it, then let the Function's timer trigger take over.
Two GitHub Actions workflows are checked in under .github/workflows/:
ci.yml- on every push and pull request, restores, builds, and runs the full test suite in both Debug and Release configurations (matrixed, so a Release-only failure is caught and reported distinctly from a Debug one).release.yml- on every published GitHub Release, re-runs the test suite in Release, thendotnet publishes both hosts, zips each, and attaches them to the Release as downloadable assets. Artifact-only for now - see step 4 above for using those artifacts, and the workflow's own comments for what an actual auto-deploy-to-Azure step would still need (a real target subscription/resource group and an OIDC-based authentication setup, neither of which exist yet).