Skip to content

OIDC & Secrets

GitHub Actions authenticates to Azure using OpenID Connect (OIDC) — no long-lived credentials are stored anywhere.

How OIDC Works

Instead of a stored password or client secret, GitHub requests a short-lived JWT token from its OIDC provider during each workflow run. Azure trusts that token via a federated credential attached to the app registration. The token is scoped to a specific subject (branch, PR, environment) and expires after the run.

Setup

Run the setup script from the repo root:

bash
cd infra/scripts
chmod +x setup-oidc.sh   # Linux/macOS; Git Bash/WSL on Windows
./setup-oidc.sh

The script:

  1. Creates an Azure AD app registration named github-actions-portfolio-oidc
  2. Creates a service principal and assigns Contributor at subscription scope
  3. Adds federated credentials for all required subjects (see below)
  4. Prompts for EMAIL_ADDRESS / EMAIL_PASSWORD and sets them as GitHub Secrets
  5. Prints the three values you need to copy to GitHub Secrets

Federated Credentials

The app registration needs one federated credential per subject that Azure must trust:

Subject Workflow context
repo:SvenRelijveld1995/portfolio:ref:refs/heads/main Push to main
repo:SvenRelijveld1995/portfolio:pull_request Any PR
repo:SvenRelijveld1995/portfolio:ref:refs/tags/* Git tags
repo:SvenRelijveld1995/portfolio:environment:dev dev GitHub environment
repo:SvenRelijveld1995/portfolio:environment:prod prod GitHub environment
repo:SvenRelijveld1995/portfolio:environment:shared shared GitHub environment

Verify they exist:

bash
az ad app federated-credential list --id <AZURE_CLIENT_ID>

GitHub Secrets

Azure OIDC Secrets (required for all workflows)

Secret Value
AZURE_CLIENT_ID App registration client ID (from setup script)
AZURE_TENANT_ID Azure AD tenant ID (from setup script)
AZURE_SUBSCRIPTION_ID Azure subscription ID (from setup script)

Application Secrets

Secret Purpose
EMAIL_ADDRESS Gmail address used by the backend contact form
EMAIL_PASSWORD Gmail app password (not your Google account password)

Email secrets are written to Key Vault on every infrastructure deploy as @secure() Bicep parameters. If omitted, the backend runs in demo mode (logs email content, sends nothing).

Dev Environment Secrets (E2E auth bypass)

These secrets are set on the dev GitHub environment and used by the playwright-e2e job in deploy-pr-preview.yml to obtain an EasyAuth session cookie for the SSO-protected dev Container App.

Secret Purpose
E2E_CLIENT_ID Client ID of the portfolio-e2e-ci Entra app registration (98ffb749-c54b-431a-82f3-df1bf271ed32)
E2E_CLIENT_SECRET Client secret for portfolio-e2e-ci; used with client credentials flow to get a token for api://b30b86ca-b372-4759-b02c-55a622dd19a0 (the dev SSO app)

The ENTRA_SSO_APP_CLIENT_ID value (b30b86ca-b372-4759-b02c-55a622dd19a0) is hardcoded in the workflow (non-secret). AZURE_TENANT_ID is reused from the OIDC secrets above.


Domain Purchase Secrets (only needed if purchasing via Azure App Service Domain)

Secret Purpose
CONTACT_PHONE Registrant phone number
CONTACT_ADDRESS1 Street address
CONTACT_CITY City
CONTACT_STATE State/province
CONTACT_POSTAL_CODE Postal code

These are injected at workflow runtime — not stored in parameter files — to keep PII out of git history.

GitHub Environment Configuration

prod environment

  1. Settings → Environments → prod
  2. Enable Deployment branches: main only (+ tag/*)
  3. Optional: add required reviewers and a wait timer

shared environment

  1. Settings → Environments → shared
  2. Enable Deployment branches: main only

The dev environment has no branch restrictions (PR deployments).

Branch Protection

Recommended settings for main:

  • Require pull request reviews
  • Require status checks: validate (from validate-infrastructure.yml), test-api
  • Require branches to be up to date before merging
  • Do not allow bypassing above settings

Troubleshooting

AADSTS70021: No matching federated identity record found

A workflow ran from a subject that has no federated credential. Check which subject the workflow uses and add it:

bash
# List existing federated credentials
az ad app federated-credential list --id <AZURE_CLIENT_ID>

# Re-run setup script to add missing credentials
./infra/scripts/setup-oidc.sh

AuthorizationFailed: The client does not have authorization

The service principal is missing a role assignment:

bash
az role assignment list --assignee <AZURE_CLIENT_ID>

# If missing Contributor:
az role assignment create \
  --assignee <AZURE_CLIENT_ID> \
  --role Contributor \
  --scope /subscriptions/<SUBSCRIPTION_ID>

Workflow not triggering

Check that path filters in the workflow file match the files you changed, and that the branch is not blocked by branch protection or environment restrictions.

Next Steps

Page history

Field Value
Last updated 2026-03-20

Changelog

Date PRs Summary
2026-03-10 #66 Extracted OIDC & Secrets page from github-actions-setup; federated credentials, GitHub Secrets, environment config
2026-03-20 #83 Added dev environment E2E CI secrets (E2E_CLIENT_ID, E2E_CLIENT_SECRET) for Playwright Entra ID SSO bypass.