This document describes how to securely manage secrets for IssueTrackerApp across different environments.
The application uses a layered configuration system where secrets are never stored in source control:
| Environment | Secret Storage | Access Method |
|---|---|---|
| Development | .NET User Secrets | dotnet user-secrets CLI |
| CI/CD | GitHub Repository Secrets | Environment variables |
| Production | Azure Key Vault | Managed Identity |
| Secret | Description | Required |
|---|---|---|
Auth0:Domain |
Auth0 tenant domain (e.g., tenant.auth0.com) |
✅ Yes |
Auth0:ClientId |
Auth0 application client ID | ✅ Yes |
Auth0:ClientSecret |
Auth0 application client secret | ✅ Yes |
MongoDB:ConnectionString |
MongoDB Atlas connection string | ✅ Production |
SendGrid:ApiKey |
SendGrid API key for email | Optional |
BlobStorage:ConnectionString |
Azure Blob Storage connection | Optional |
- .NET SDK 10.0+ installed
- Terminal/PowerShell open at the repository root
User secrets are project-specific. You must run commands from the Web project directory:
# From the repository root (E:\github\IssueTrackerApp)
cd src\WebThe Web.csproj already has a UserSecretsId. Verify it exists:
# Should show: <UserSecretsId>issuetracker-web-secrets</UserSecretsId>
Select-String -Path Web.csproj -Pattern "UserSecretsId"Get your Auth0 credentials from the Auth0 Dashboard:
- Go to Applications → Applications → Your App
- Copy Domain, Client ID, and Client Secret
# Still in src\Web directory
dotnet user-secrets set "Auth0:Domain" "YOUR-TENANT.us.auth0.com"
dotnet user-secrets set "Auth0:ClientId" "YOUR_CLIENT_ID_HERE"
dotnet user-secrets set "Auth0:ClientSecret" "YOUR_CLIENT_SECRET_HERE"For local development, .NET Aspire runs MongoDB in a container automatically — no connection string needed!
For MongoDB Atlas (cloud database), get your connection string from MongoDB Atlas:
- Go to Database → Connect → Drivers
- Copy the connection string (replace
<password>with your actual password)
dotnet user-secrets set "MongoDB:ConnectionString" "mongodb+srv://username:password@cluster.mongodb.net/issuetracker-db?retryWrites=true&w=majority"# List all secrets (still in src\Web)
dotnet user-secrets listExpected output:
Auth0:ClientId = abc123...
Auth0:ClientSecret = xyz789...
Auth0:Domain = your-tenant.us.auth0.com
MongoDB:ConnectionString = mongodb+srv://...
Secrets are stored outside the repository in your user profile:
| OS | Location |
|---|---|
| Windows | %APPDATA%\Microsoft\UserSecrets\issuetracker-web-secrets\secrets.json |
| macOS | ~/.microsoft/usersecrets/issuetracker-web-secrets/secrets.json |
| Linux | ~/.microsoft/usersecrets/issuetracker-web-secrets/secrets.json |
You can view/edit this file directly if needed.
Go to Settings → Secrets and variables → Actions and add:
| Secret Name | Value |
|---|---|
AUTH0_DOMAIN |
Your Auth0 tenant domain |
AUTH0_CLIENT_ID |
Your Auth0 client ID |
AUTH0_CLIENT_SECRET |
Your Auth0 client secret |
MONGODB_CONNECTION_STRING |
MongoDB Atlas connection string |
AZURE_CREDENTIALS |
Azure service principal JSON (for Key Vault access) |
Secrets are injected as environment variables in workflows:
env:
Auth0__Domain: ${{ secrets.AUTH0_DOMAIN }}
Auth0__ClientId: ${{ secrets.AUTH0_CLIENT_ID }}
Auth0__ClientSecret: ${{ secrets.AUTH0_CLIENT_SECRET }}
MongoDB__ConnectionString: ${{ secrets.MONGODB_CONNECTION_STRING }}Note: Use double underscores (
__) for nested configuration in environment variables.
- Azure CLI installed
- Logged in:
az login - An Azure subscription
az group create \
--name issuetracker-rg \
--location eastusKey Vault names must be globally unique (3-24 characters, alphanumeric and hyphens only):
az keyvault create \
--name issuetracker-kv \
--resource-group issuetracker-rg \
--location eastus \
--sku standardAzure Key Vault uses -- as the section separator (translated to : by .NET):
# Auth0 secrets
az keyvault secret set --vault-name issuetracker-kv --name "Auth0--Domain" --value "your-tenant.us.auth0.com"
az keyvault secret set --vault-name issuetracker-kv --name "Auth0--ClientId" --value "your-client-id"
az keyvault secret set --vault-name issuetracker-kv --name "Auth0--ClientSecret" --value "your-client-secret"
# MongoDB Atlas connection string
az keyvault secret set --vault-name issuetracker-kv --name "MongoDB--ConnectionString" --value "mongodb+srv://user:pass@cluster.mongodb.net/issuetracker-db"
# Optional: SendGrid
az keyvault secret set --vault-name issuetracker-kv --name "SendGrid--ApiKey" --value "SG.your-api-key"# Create Container Apps environment
az containerapp env create \
--name issuetracker-env \
--resource-group issuetracker-rg \
--location eastus
# Deploy the app with managed identity enabled
az containerapp create \
--name issuetracker-app \
--resource-group issuetracker-rg \
--environment issuetracker-env \
--image your-registry/issuetracker:latest \
--target-port 8080 \
--ingress external \
--system-assigned# Get the app's managed identity principal ID
PRINCIPAL_ID=$(az containerapp identity show \
--name issuetracker-app \
--resource-group issuetracker-rg \
--query principalId -o tsv)
# Grant the identity permission to read secrets
az keyvault set-policy \
--name issuetracker-kv \
--object-id $PRINCIPAL_ID \
--secret-permissions get list# Set the Key Vault URI as an environment variable
az containerapp update \
--name issuetracker-app \
--resource-group issuetracker-rg \
--set-env-vars "KeyVault__Uri=https://issuetracker-kv.vault.azure.net/"For App Service instead of Container Apps:
# Enable managed identity
az webapp identity assign --name issuetracker-app --resource-group issuetracker-rg
# Get principal ID
PRINCIPAL_ID=$(az webapp identity show --name issuetracker-app --resource-group issuetracker-rg --query principalId -o tsv)
# Grant Key Vault access (same as above)
az keyvault set-policy --name issuetracker-kv --object-id $PRINCIPAL_ID --secret-permissions get list
# Set Key Vault URI in app settings
az webapp config appsettings set --name issuetracker-app --resource-group issuetracker-rg --settings KeyVault__Uri=https://issuetracker-kv.vault.azure.net/SendGrid is used for email notifications (issue assignments, status changes, comments). This section covers how to create a SendGrid account, generate an API key, and configure it for IssueTrackerApp.
- Go to SendGrid Signup
- Create a free account (100 emails/day free tier)
- Complete email verification and account setup
Before sending emails, you must verify a sender:
- Go to Settings → Sender Authentication
- Choose one of:
- Single Sender Verification (easiest for dev) — Verify one email address
- Domain Authentication (recommended for production) — Verify your domain via DNS
For Single Sender:
- Click Create a Sender
- Fill in your details (From Name, From Email, etc.)
- Click the verification link sent to that email
- Go to Settings → API Keys
- Click Create API Key
- Enter a name:
IssueTrackerApp - Choose Restricted Access and enable:
- Mail Send → Full Access
- Click Create & View
- Copy the key immediately — it won't be shown again!
The API key looks like: SG.xxxxxx.yyyyyy (starts with SG.)
# From src\Web directory
dotnet user-secrets set "SendGrid:ApiKey" "SG.your-api-key-here"
dotnet user-secrets set "SendGrid:FromEmail" "noreply@yourdomain.com"
dotnet user-secrets set "SendGrid:FromName" "IssueTracker"Add to repository secrets (Settings → Secrets → Actions):
| Secret Name | Value |
|---|---|
SENDGRID_API_KEY |
SG.your-api-key |
SENDGRID_FROM |
noreply@yourdomain.com |
az keyvault secret set --vault-name issuetracker-kv --name "SendGrid--ApiKey" --value "SG.your-api-key"
az keyvault secret set --vault-name issuetracker-kv --name "SendGrid--FromEmail" --value "noreply@yourdomain.com"
az keyvault secret set --vault-name issuetracker-kv --name "SendGrid--FromName" --value "IssueTracker"The base configuration (no secrets) should look like:
{
"SendGrid": {
"ApiKey": "",
"FromEmail": "",
"FromName": "IssueTracker"
}
}After configuring, test email sending:
- Run the application locally
- Create an issue and assign it to a user with a valid email
- Check the SendGrid Activity Feed for sent emails
- Check spam folder if email doesn't arrive
| Problem | Solution |
|---|---|
| "Unauthorized" error | API key is invalid or missing — check SendGrid:ApiKey is set |
| "Forbidden" error | API key doesn't have Mail Send permission — regenerate with correct scopes |
| Emails not arriving | Check sender is verified; check recipient's spam folder |
| "From address not verified" | Complete Sender Authentication in SendGrid dashboard |
| Rate limited | Free tier allows 100/day — upgrade or wait 24 hours |
- Use a subdomain for sending (e.g.,
mail.yourdomain.com) to protect your main domain reputation - Set up domain authentication for production to improve deliverability
- Monitor the Activity Feed to track bounces and blocks
- Use templates for consistent email formatting (SendGrid Dynamic Templates)
- Don't share API keys between environments — create separate keys for dev/staging/prod
If you prefer SMTP over the SendGrid API, you can use SendGrid's SMTP relay:
dotnet user-secrets set "Email:SmtpHost" "smtp.sendgrid.net"
dotnet user-secrets set "Email:SmtpPort" "587"
dotnet user-secrets set "Email:SmtpUsername" "apikey"
dotnet user-secrets set "Email:SmtpPassword" "SG.your-api-key"
dotnet user-secrets set "Email:FromEmail" "noreply@yourdomain.com"Note: For SMTP, the username is literally the word
apikey, and the password is your API key.
.NET loads configuration in this order (later sources override earlier):
appsettings.json— Base defaults (empty strings for secrets)appsettings.{Environment}.json— Environment-specific non-secrets- User Secrets — Development only
- Azure Key Vault — Non-Development environments
- Environment Variables — Highest priority (CI/CD, containers)
- Use User Secrets for local development
- Use Azure Key Vault for production secrets
- Use GitHub Repository Secrets for CI/CD
- Enable Managed Identity for Azure services
- Rotate secrets regularly
- Use separate secrets per environment
- Commit secrets to source control
- Use placeholder values that look like real credentials
- Share development secrets across team members
- Store secrets in appsettings.json files
- Log secrets in application logs
- Are you in the right directory? Run
pwd— you should be insrc/Web - Does UserSecretsId exist? Check Web.csproj for
<UserSecretsId> - Is ASPNETCORE_ENVIRONMENT set? User secrets only load in Development mode
- List your secrets:
dotnet user-secrets list(run fromsrc/Web)
- Is KeyVault:Uri set? Check your app's environment variables
- Is Managed Identity enabled?
az containerapp identity show --name your-app --resource-group your-rg - Does the identity have access? Check Key Vault access policies in Azure Portal
- Are secret names correct? Use
--not:(e.g.,Auth0--DomainnotAuth0:Domain)
- Are all three secrets set? Domain, ClientId, AND ClientSecret are required
- Is the callback URL configured? In Auth0 Dashboard → Application → Allowed Callback URLs, add:
- Development:
https://localhost:5001/callback - Production:
https://your-domain.com/callback
- Development:
- Is it a "Regular Web Application"? Not SPA or Native
- Is your IP whitelisted? In MongoDB Atlas → Network Access, add your IP or
0.0.0.0/0for testing - Is the password URL-encoded? Special characters like
@or#must be encoded - Is the database name correct? Check the connection string includes
/issuetracker-db
# Navigate to Web project (required for user-secrets commands)
cd src\Web
# Set a secret
dotnet user-secrets set "Section:Key" "value"
# List all secrets
dotnet user-secrets list
# Remove a secret
dotnet user-secrets remove "Section:Key"
# Clear all secrets
dotnet user-secrets clear| Context | Format Example |
|---|---|
| User Secrets / Code | Auth0:Domain |
| Environment Vars | Auth0__Domain |
| Azure Key Vault | Auth0--Domain |