enum

TL;DR

  • Use my demo OpenID Connect provider to test workload identity federation in Entra ID.
  • Use my Terraform module and example to create an Azure AD application with federated identity credentials.
  • Play with workload identity federation without the need for secrets.
  • Integrate your workloads with external identity providers like GitHub or Kubernetes.

Introduction

No more secrets! It is 2025, and our identity operations should be more secure and easier to manage. Microsoft Entra ID Workload Identity Federation enables you to utilise external identity providers (such as GitHub, Workload on Kubernetes cluster, SPIFFE, or SPIRE) to authenticate workloads without requiring secrets. In this post, I’ll guide you through setting up federated identity credentials in Entra ID using a custom OIDC provider and Terraform.

Big Picture

workload-identity-federation

MS Documentation

Token provider

Instead of using GitHub as an example, to make it simple and easy to test and make the demo, I created a simple OpenID Connect provider with the following endpoints:

GET https://api.demo.factorlabs.pl/.well-known/openid-configuration

Example response:

{
  "issuer": "https://api.demo.factorlabs.pl",
  "jwks_uri": "https://api.demo.factorlabs.pl/.well-known/keys",
  "id_token_signing_alg_values_supported": [
    "RS256"
  ]
}

And of course, the key’s endpoint, to validate the JWT (Json Web Token) signature - and prove that the token is issued by the provider:

GET https://api.demo.factorlabs.pl/.well-known/keys

Example response:

{
  "keys": [
    {
      "kid": "526F177AE1470D5B73C202B085997D41EA99A8BE",
      "nbf": 1749577799,
      "use": "sig",
      "kty": "RSA",
      "alg": "RS256",
      "x5c": [
        "MIIFEDCCAs..."
      ],
      "x5t": "Um8XeuFHDVtzwgKwhZl9QeqZqL4",
      "n": "pXD23T...",
      "e": "AQAB"
    }
  ]
}

My demo token provider (endpoint). The endpoint generates a JWT token with a fixed subject and audience. With the Entra ID token endpoint, you can exchange the token for an access token to call the Microsoft Graph API or other API protected by Entra ID. enum

POST https://api.demo.factorlabs.pl/api/workload/token

Example token (JWT encoded) provided by the demo OpenID Connect provider in the point above:

{
  "aud": "api://AzureADTokenExchange",
  "iss": "https://api.demo.factorlabs.pl",
  "exp": 1749582266,
  "iat": 1749581651,
  "sub": "system:serviceaccount:default:play-with-workload-identity"
}

Terraform code

The example below creates an Azure AD application with federated identity credentials, allowing workload identity federation without the need for secrets. It uses the azuread provider to manage Azure Active Directory resources.

Terraform Module

variable "graph_permissions" {
    description = "List of Graph API permissions"
    type        = list(string)
    default     = []
}
variable "business_name" {
    description = "Business name"
    type        = string
}
variable deployment_env_name {
  description = "Unique name for the deployment"
  type        = string
  default     = "Workshop"
}
variable "enable_workload_identity" {
    description = "Enable workload identity federation"
    type        = bool
}
variable "subject_identifier" {
    description = "Subject identifier for the federated credential"
    type        = string
}
variable "issuer_url" {
    description = "Issuer URL for the federated credential"
    type        = string
}

resource "azuread_application" "this" {
  display_name     = "TF.${var.deployment_env_name}.${var.business_name}.ServicePrincipal"
  sign_in_audience = "AzureADMyOrg"
  api {
    mapped_claims_enabled          = true
    requested_access_token_version = 2
  }
  feature_tags {
    enterprise = true
    gallery    = true
  }
  required_resource_access {
    # Microsoft Graph
    resource_app_id = "00000003-0000-0000-c000-000000000000"
    dynamic "resource_access" {
      for_each = var.graph_permissions
      content {
        id   = resource_access.value
        type = "Role"
      }
    }
  }
}

resource "azuread_service_principal" "this" {
  client_id                    = azuread_application.this.client_id
  app_role_assignment_required = false
}

resource "azuread_application_federated_identity_credential" "this" {
  count              = var.enable_workload_identity ? 1 : 0
  application_id     = azuread_application.this.id
  display_name       = "TF.${var.deployment_env_name}.${var.business_name}.FederatedCredential"
  description        = "Workload Identity federation for ${var.business_name}"
  audiences          = ["api://AzureADTokenExchange"]
  issuer             = "${var.issuer_url}"
  subject            = var.subject_identifier
}

output "application_id" {
  value = azuread_application.this.id
}

output "application_client_id" {
  value = azuread_application.this.client_id
}

output "service_principal_id" {
  value = azuread_service_principal.this.id
}

Example Usage

module "Demo_WorkloadIdentity_ServicePrincipal" {
  source = "./modules/service_principal_workload_identity"
  business_name = "${var.deployment_unique_name}-WorkloadIdentity"
  enable_workload_identity = true
  subject_identifier = "system:serviceaccount:default:play-with-workload-identity"
  issuer_url = "https://api.demo.factorlabs.pl"
  graph_permissions = [
    #application.read.all
    "PASTE_YOUR_GRAPH_PERMISSION_GUID_HERE"
    ]
}

Get Entra ID Token

When we deploy the above Terraform code, we can get the token for the service principal using the following HTTP request. Please remember to provide your token, tenantId and client ID for service principal!

Feel free to use my token provider and integrate with your Entra ID to test it.

^ Do not use my demo token provider with your production environments!

POST https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={{clientId}}&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion={{subjectToken}}&
grant_type=client_credentials&
scope=https://graph.microsoft.com/.default

Summary

  • JWT token subject (sub) is the same as the subject in the azuread_application_federated_identity_credential resource.
  • JWT token audience (aud) is set to “api://AzureADTokenExchange” by default based on the REST API Documentation.
  • JWT issuer (iss) must be the host for .well-known endpoint, the key must be available at <issuer_url>/.well-known/keys.

Side Note