Join our Newsletter — 33% off our NHI Course
Home› Guides› How to set up workload identity federation for…
Guide Non-Human Identity (NHI)

How to set up workload identity federation for GitLab CI to Azure: Secretless Azure deployments from GitLab pipelines, step by step

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 28 October 2026 11 min read
On this page

A client secret for an Azure service principal stored as a GitLab CI/CD variable is a long-lived credential that every maintainer, every job log and every compromised runner can expose. Workload identity federation removes it: the GitLab job receives a short-lived ID token signed by GitLab, and Microsoft Entra ID exchanges it for an Azure access token only if the token's issuer, subject and audience match a federated identity credential you defined. This page walks through the setup with the Azure CLI, how to prove it works, how to roll back, and how to tighten the trust once it runs. It is for platform and DevOps engineers on GitLab.com, GitLab Self-Managed or GitLab Dedicated who deploy to Azure. For the concepts behind the exchange see the Workload Identity Federation Guide.

Key takeaways

  • Three values must match exactly. Entra compares the federated credential's issuer, subject and audience case-sensitively with the iss, sub and aud claims of the GitLab ID token, and wildcards are not supported in a standard federated credential.
  • A wrong subject fails silently. Microsoft documents that a credential with the wrong subject is created without error and only fails at token exchange, so test every new credential in a pilot job.
  • Use ID tokens, not the old JWT variables. CI_JOB_JWT_V2 was removed in GitLab 17.0; declare an ID token with the id_tokens keyword and pass it to az login --federated-token.
  • Bind trust to IDs, not paths. The default subject uses the project path. GitLab lets you switch the subject to start with project_id, which survives renames and moves and avoids inheriting trust through a recycled path.
  • Scope the role, not just the token. Federation removes the secret but not the blast radius. Grant the service principal a narrow role at resource group or resource scope, never Owner on a subscription.

When you need this

  • You have AZURE_CLIENT_SECRET or a certificate in CI/CD variables. Any job that can read the variable can copy it, and the secret works from anywhere until it expires. Replacing it with federation is the main reason to follow this page.
  • Secret rotation is causing outages. Client secrets expire; federated credentials have no secret to expire, and the ID token itself expires with the job's timeout, or after 5 minutes if no timeout is set.
  • You need separate access per branch or environment. One federated credential per subject lets production deployments use a different identity from feature branch jobs.
  • Your secrets have leaked before. Tokens and connection strings stored alongside code are a recurring source of incidents; in the Red Hat Consulting GitLab breach, attackers who copied a GitLab instance claimed it contained customer tokens and database connection strings.

Do not use this pattern if your GitLab Self-Managed instance cannot be reached from the internet. Entra must fetch /.well-known/openid-configuration and /oauth/discovery/keys from the issuer to validate the token signature.

Before you start

RequirementDetail
Azure permissionsRights to assign roles at the target scope (GitLab's tutorial assumes Owner on the subscription; a narrower role assignment right at resource group scope is enough for the assignment step)
Entra permissionsTo create the app, at least Application Developer. To add or change federated credentials: app owner, or Application Administrator, Cloud Application Administrator, Global Administrator or Hybrid Identity Administrator
ToolsAzure CLI locally or Azure Cloud Shell; a GitLab project where you can edit .gitlab-ci.yml
Issuerhttps://gitlab.com for GitLab.com, or your instance URL (for example https://gitlab.example.com), publicly reachable over HTTPS
Identity choiceAn app registration with service principal (used below), or a user-assigned managed identity with az identity federated-credential create
Decisions to make firstWhich branch, tag or environment may deploy; which Azure scope and role; one audience value (Microsoft recommends api://AzureADTokenExchange)
OptionChoose it whenTrade-off
App registration and service principalYou need Microsoft Graph or multi-tenant access, or the identity is managed by an Entra teamLives in Entra; creating federated credentials needs app ownership or an application administrator role
User-assigned managed identityThe pipeline only touches Azure resources and you want the identity managed with the resource group in Azure Resource ManagerSign-ins appear in the managed identity sign-in log; federated credentials on one identity must be created one at a time, as concurrent writes return 409

Limits worth knowing before design: a maximum of 20 federated identity credentials per application or user-assigned managed identity, the issuer and subject pair must be unique on the app, and the credential name (3 to 120 characters) cannot be changed after creation.

Step-by-step

  1. Create the app registration and service principal.
    appId=$(az ad app create --display-name gitlab-oidc-deploy-prod --query appId -otsv)
    az ad sp create --id $appId --query appId -otsv
    Record the application (client) ID and your tenant ID. Name the app after the pipeline and environment so the sign-in logs are readable.
  2. Decide the subject. By default GitLab issues sub as project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}, for example project_path:platform/payments-api:ref_type:branch:ref:main. To bind trust to the immutable project ID, set the project's ci_id_token_sub_claim_components to ["project_id", "ref_type", "ref"] through the projects API (update a project); the subject becomes project_id:{id}:ref_type:{type}:ref:{branch_name}.
  3. Create the federated identity credential. Save as credential.json:
    {
      "name": "gitlab-payments-api-main",
      "issuer": "https://gitlab.com",
      "subject": "project_path:platform/payments-api:ref_type:branch:ref:main",
      "description": "GitLab CI deploy from main",
      "audiences": ["api://AzureADTokenExchange"]
    }
    Then run:
    az ad app federated-credential create --id $appId --parameters credential.json
    Use your own instance URL as the issuer on Self-Managed or Dedicated, with no trailing whitespace: Entra blocks the exchange if the issuer claim has leading or trailing whitespace.
  4. Grant a narrow Azure role.
    az role assignment create --assignee $appId --role "<least-privileged role>" \
      --scope /subscriptions/<sub-id>/resourceGroups/<rg-name>
    GitLab's tutorial uses Reader on the whole subscription only to demonstrate; scope production deployments to the resource group or resource they change.
  5. Request the ID token in the job and log in.
    deploy-prod:
      image: mcr.microsoft.com/azure-cli:latest
      variables:
        AZURE_CLIENT_ID: "<client-id>"
        AZURE_TENANT_ID: "<tenant-id>"
      id_tokens:
        GITLAB_OIDC_TOKEN:
          aud: api://AzureADTokenExchange
      script:
        - az login --service-principal -u $AZURE_CLIENT_ID -t $AZURE_TENANT_ID --federated-token $GITLAB_OIDC_TOKEN
        - az account show
      rules:
        - if: $CI_COMMIT_BRANCH == "main"
    The client ID and tenant ID are identifiers, not secrets. The aud in id_tokens must equal the credential's audience.
  6. Allow for propagation. Microsoft notes that a token request made several minutes after creating a credential may still fail with AADSTS70021 while caches update. Wait and rerun before changing anything.

Several branches or tags: a standard credential matches one exact subject. For "any branch" or "any tag" GitLab documents flexible federated identity credentials, which use a claimsMatchingExpression such as claims['sub'] matches 'project_path:<group>/<project>:ref_type:branch:ref:*'. Microsoft lists this feature as preview and, for GitLab with mutable (path-based) subjects, requires the expression to match both sub and project_id. Prefer protected environments and exact subjects for production.

Verify it worked

  • Job output: az account show prints the expected tenant and subscription, and a read of the target resource group succeeds while a read outside the assigned scope fails.
  • Entra sign-in logs: the sign-in appears in the service principal sign-in log for the app (user-assigned managed identities appear in the managed identity sign-in log instead). Check the Client credential type field to confirm it was a federated credential, not a secret.
  • Negative test: run the same job from a feature branch. It must fail with AADSTS70021, proving the subject restriction works.
  • Secret removal: list remaining secrets and certificates on the app with az ad app credential list --id $appId; the target state is none.

Rollback and common errors

ErrorLikely causeFix
AADSTS70021: No matching federated identity record found for presented assertion.Issuer, subject or audience differs from the token, or the credential has not propagatedWait a few minutes; then compare values case-sensitively, including group and project path from the project URL
AADSTS700212 no matching record for presented assertion audienceThe job's id_tokens aud differs from the credential's audienceUse the same single audience in both places
AADSTS501661: Request to External OIDC endpoint failed.Entra cannot reach the GitLab discovery or keys endpointExpose /.well-known/openid-configuration and /oauth/discovery/keys; GitLab suggests clearing the Redis cache after firewall changes
ID token issuance is disabledThe project path in sub was previously used by another projectSwitch ci_id_token_sub_claim_components to start with project_id and update the credential subject
GitLab 401: unauthorized from the target serviceOld CI_JOB_JWT_V2 used, or no id_tokens blockDeclare id_tokens in the job

Rollback: keep the old client secret valid but unused during a pilot window. If federation fails in production, revert the job to the secret-based login in one merge request, then fix the credential. Once the federated job has run cleanly through a full release cycle, delete the secret (az ad app credential delete --id $appId --key-id <key-id>) and the CI/CD variable. To remove federation, delete the credential with az ad app federated-credential delete --id $appId --federated-credential-id <id>; access stops for new exchanges, while access tokens already issued remain valid until they expire.

Hardening afterwards

  • Use protected branches and environments. GitLab notes that merge request pipelines from a different project have ref_protected:false; if you use job_project_path or job_project_id subjects, add ref_protected to the subject components and require true. Pair production credentials with a protected environment so only approved deployments run.
  • One identity per environment. Separate app registrations for production and non-production keep a compromised test pipeline from reaching production. Remember the 20-credential limit when planning.
  • Guard who can add credentials. Anyone who can add a federated credential to the app can trust a new issuer and subject, which is a persistence route (ATT&CK T1098.001 Additional Cloud Credentials). Keep app owners minimal and review the Application Administrator and Cloud Application Administrator role holders. For user-assigned managed identities, Microsoft shows an Azure Policy deny rule on Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials to stop unapproved creation.
  • Harden runners. The ID token is a bearer credential for its lifetime. Set short job timeouts, since the token's expiry follows the job timeout, and prefer ephemeral runners for production deployments.
  • Alert on change. Monitor Entra audit logs for federated credential changes on deployment apps and service principal sign-ins from unexpected IP ranges. See the GitLab Tokens Security Guide for the other GitLab credentials worth retiring.
MetricTarget
Deployment app registrations with client secrets or certificatesZero after migration
Federated credentials using path-based subjects for productionFalling towards zero as projects move to project_id
Federated credential changes without a change ticketZero
Service principals with subscription-wide Owner or Contributor used by CIZero
FrameworkReferenceRelevance
MITRE ATT&CKT1552.001 Credentials In Files; T1078.004 Cloud Accounts; T1098.001 Additional Cloud CredentialsSecrets in CI files and variables; use of the service principal; rogue federated credentials as persistence
NIST SP 800-53 Rev. 5IA-5, AC-2, AC-6, AU-6Authenticator management, account management, least privilege, log review
NIST CSF 2.0PR.AA-01, PR.AA-05, DE.CM-03Credential management, least privilege, monitoring

Practitioner checklist

  • Inventory every Azure client secret and certificate held in GitLab CI/CD variables.
  • Create one app registration or managed identity per environment, with a least-privileged role at resource group scope.
  • Switch production projects to project_id-based subjects and create exact-match federated credentials.
  • Use one audience value, api://AzureADTokenExchange, in both id_tokens and the credential.
  • Run a positive test from the allowed branch and a negative test from a feature branch; confirm the credential type in the sign-in log.
  • Delete the old client secret and CI/CD variable after a clean release cycle.
  • Restrict who can add federated credentials and alert on any change.
  • Set short job timeouts and protected environments for deployment jobs.

Standards and references

Related NHI Mgmt Group resources: Workload Identity Federation Guide · Multi-Cloud Workload Identity Troubleshooting Guide · GitLab Security Guide · How to set up workload identity federation for GitHub Actions to AWS

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 28 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.