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:
The script:
- Creates an Azure AD app registration named
github-actions-portfolio-oidc - Creates a service principal and assigns
Contributorat subscription scope - Adds federated credentials for all required subjects (see below)
- Prompts for
EMAIL_ADDRESS/EMAIL_PASSWORDand sets them as GitHub Secrets - 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:
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¶
- Settings → Environments →
prod - Enable Deployment branches:
mainonly (+tag/*) - Optional: add required reviewers and a wait timer
shared environment¶
- Settings → Environments →
shared - Enable Deployment branches:
mainonly
The dev environment has no branch restrictions (PR deployments).
Branch Protection¶
Recommended settings for main:
- Require pull request reviews
- Require status checks:
validate(fromvalidate-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:
AuthorizationFailed: The client does not have authorization¶
The service principal is missing a role assignment:
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¶
- Custom Domains — DNS zone, 3-stage cert process, shared.bicep
- Pipeline Overview — all 9 workflows and execution scenarios
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. |