Chapter 24 · Security, Raw SQL, Secrets, Authorization Boundaries, and Abuse Resistance
Protect Connection Strings and Credentials with Environment Configuration and Secret Stores
Separate configuration from secrets, keep database credentials out of source and diagnostics, use .NET User Secrets only for local development, and adopt deployment-specific secret stores or identity-based authentication without making cloud services mandatory for the course lab.
Learning outcomes
Separate ordinary configuration values from secrets and identify which connection-string components create credential risk.
Use .NET User Secrets for local development without mistaking it for encrypted or production-grade storage.
Use environment variables carefully and explain why they are configuration transport, not guaranteed secret isolation.
Integrate deployment secret stores or identity-based authentication where available while preserving a free local path.
Rotate credentials and connection pools deliberately without logging raw connection strings.
Build tests and startup diagnostics that prove configuration source/shape without exposing secret values.
1. The connection string is part configuration, part authority
A database connection string may contain a server address, database name, encryption settings, application name, pool options, user name, password, access token mode, or local file path. Some fields are operational configuration; others are credentials. Treating the whole string as ordinary text risks committing secrets to Git, printing them in logs, copying production passwords into tests, or leaking them through exception diagnostics.
Assume source repositories, build logs, support bundles, crash reports, test artifacts and developer chat transcripts may be seen by more people than the production database credential should be.
2. Keep committed appsettings free of credentials
{ "ConnectionStrings": { "ServiceHub": "Data Source=servicehub-lab.db" }, "Database": { "Provider": "Sqlite" }}
The mandatory local SQLite path needs no password, which makes it ideal for course labs. For a server provider, commit only non-secret defaults or a configuration key name—never a production password, token, private key, or connection string containing one.
3. User Secrets: useful for development, intentionally not a vault
The .NET Secret Manager keeps development secrets outside the
project tree so they are not normally checked into source
control. Microsoft explicitly states that User Secrets values
are not encrypted and the mechanism is for
development only. The next snippet uses ASP.NET Core’s
WebApplicationBuilder, which combines .NET
configuration with dependency-injection registration; a
console/worker host can consume the same configuration keys with
the generic host/configuration packages.
dotnet user-secrets initdotnet user-secrets set "ConnectionStrings:ServiceHubSqlServer" "Server=localhost,1433;Database=ServiceHub;User Id=servicehub_dev;Password=local-dev-only;Encrypt=True;TrustServerCertificate=True"
var builder = WebApplication.CreateBuilder(args);var connectionString = builder.Configuration .GetConnectionString("ServiceHubSqlServer") ?? throw new InvalidOperationException("Database connection is not configured.");builder.Services.AddDbContext<ServiceHubContext>(options => options.UseSqlServer(connectionString));
Do not call Console.WriteLine(connectionString) to
“verify” configuration. Instead, log a safe provider/environment
label or perform a health check that reports success/failure
without returning credentials.
4. Environment variables can override configuration, but may still be visible
.NET configuration uses environment variables naturally, including the cross-platform double-underscore separator. This avoids embedding a secret in the app package, but Microsoft warns that environment variables are commonly stored as plain text and can be read if the host/process is compromised.
export ConnectionStrings__ServiceHub="${SERVICEHUB_DB_CONNECTION}"export Database__Provider="PostgreSql"
Treat the environment as a transport mechanism whose security depends on the orchestrator/host. Restrict who can inspect deployment configuration and prefer a managed secret service when the platform provides one.
5. Production secret stores and managed identity are deployment choices
A production secret store centralizes secret access, audit, rotation and access policy. Azure Key Vault is one example; other clouds and on-premises platforms have equivalents. Identity-based database authentication can sometimes remove the database password from application configuration entirely—for example, Azure SQL with Microsoft Entra/managed identity—but that is provider/platform-specific and not mandatory for this course.
| Environment | Recommended pattern | Important limitation |
|---|---|---|
| Local SQLite lab | no credential; disposable file | file contents/path may still be sensitive |
| Local server development | .NET User Secrets or dedicated local secret tooling | User Secrets are not encrypted |
| Container/CI | CI secret injection + test-only credential | avoid production secrets; mask logs/artifacts |
| Cloud production | managed secret store and/or workload identity | platform-specific setup and permissions |
| On-prem production | enterprise secret manager / integrated auth where supported | rotation and service identity must be operationally owned |
6. Deliberate failure: “temporarily” commit the password and rely on deletion later
{ "ConnectionStrings": { "ServiceHub": "Server=db.prod;Database=ServiceHub;User Id=app;Password=P@ssw0rd!" }}
Deleting the line in a later commit does not erase repository history, forks, build logs, caches, screenshots, or copied artifacts. Treat an accidentally committed secret as compromised: revoke/rotate it, review access logs, then remove it from active configuration. History rewriting may reduce future exposure but does not make the old credential trustworthy again.
7. Rotation and pool behavior need an operational plan
Server-provider connection pools can keep physical connections alive after configuration changes. Rotating a credential therefore requires more than updating a secret store: deploy/reload safely, ensure new connections authenticate, and retire/clear old pooled connections according to the provider’s supported mechanism. Do not teach an arbitrary “restart everything” rule; test the chosen provider and hosting model.
1. create/enable new credential or identity grant2. update secret reference / identity policy3. canary opens new connection successfully4. deploy/recycle according to provider pool behavior5. revoke old credential6. confirm authentication failures did not spike7. audit that old credential is no longer accepted
8. Safe startup and support diagnostics
Startup logs should record which provider/configuration mode is active, not the secret. A sanitized connection descriptor may include a known environment name and database logical name only if your threat model permits it. For SQLite, even a file path can reveal usernames or tenant locations, so classify it before logging.
logger.LogInformation( "Database configured. Provider={Provider}; Environment={Environment}", providerName, app.Environment.EnvironmentName);// Never:// logger.LogInformation("Database={ConnectionString}", connectionString);
9. Mandatory lab: move a credential out of source and prove it stays out
- Keep the mandatory SQLite lab as the zero-secret baseline.
- Create a test-only server-provider connection string using a local account, never production credentials.
- Initialize .NET User Secrets and store the connection string there.
-
Load it through
IConfiguration; run a connectivity/migration-status probe without printing the string. -
Verify
git diff/git grepover the exercise project contains no password/token. - Override the same configuration key via an environment variable and explain the host-visibility risk.
- Write a rotation checklist for the chosen provider and remove the local test secret when finished.
10. Production judgment and bridge
Secrets management is lifecycle management: creation, delivery, use, logging discipline, rotation, revocation and audit. EF Core consumes a configured connection; it does not secure the credential store. Lesson 4 asks a more important question: once the application is connected, how much authority should that runtime identity actually have?
Check your understanding
- Why are .NET User Secrets not a production vault?
- Are environment variables automatically secure secret storage?
- What should happen after a credential is committed to Git?
- Why avoid logging a full connection string?
- What can managed identity change?
- Why does credential rotation interact with connection pooling?
Review the answers
1. They are development-only and are not encrypted; their purpose is to keep secrets outside the project tree/source control.
2. No. They can be plain text and may be visible to a compromised process/host or privileged operators.
3. Treat it as compromised: rotate/revoke it and investigate exposure; deleting the line later is insufficient.
4. It may contain passwords/tokens and can expose hosts, database names or filesystem paths useful to attackers.
5. Where supported, it can replace stored database passwords with identity-based authentication, but it is provider/platform-specific.
6. Existing physical connections can outlive a configuration change, so rotation must account for provider pool/reconnect behavior and verify new authentication.
Authoritative references
- Safe storage of app secrets in ASP.NET Core (.NET 10) — User Secrets, environment variables and development-secret warnings
- ASP.NET Core configuration (.NET 10) — configuration providers and environment-variable behavior
- Azure Key Vault configuration provider — optional managed secret-store example
- Azure SQL Microsoft Entra authentication — optional identity-based SQL authentication path
- OWASP Secrets Management Cheat Sheet — secret lifecycle, rotation and access-control guidance
- Microsoft.Data.Sqlite connection strings — free local provider configuration reference