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
MS Documentation
- Basic: https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-overview
- https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation
- token exchange: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential
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.
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
.