Skip to content

Microsoft Entra ID

The EQTY Governance Platform integrates with Microsoft Entra ID (formerly Azure Active Directory) for enterprise-grade authentication and authorization.

Required app registrations:

  • Governance Platform Backend API – Web application for backend API authentication and token validation
  • Microsoft Graph API Access – Separate app registration for user and organization management via Microsoft Graph
  • Governance Worker – M2M confidential client for automated governance workflow execution
  • Governance Studio SPA – Single Page Application for frontend authentication

This guide covers:

  • Creating app registrations in Entra ID (Azure CLI or Portal)
  • Configuring API permissions and scopes
  • Setting up Microsoft Graph access
  • Post-installation organization and admin user setup
  • Configuring the Helm chart for Entra ID
  • Microsoft Entra ID tenant (Azure AD tenant)
  • Azure CLI installed and configured (for CLI method)
  • Permissions to create app registrations in the tenant
  • Tenant ID (found in Azure Portal > Microsoft Entra ID > Overview)

Add the following to the values.yaml and secrets.yaml files. Placeholders will be filled in throughout the steps below.

values.yaml:

auth-service:
config:
idp:
provider: "entra"
issuer: "https://login.microsoftonline.com/<tenant-id>/v2.0" # Filled in after step 1
entra:
tenantId: "<tenant-id>" # Filled in after step 1
defaultRoles: "user"
governance-service:
config:
authProvider: "entra"
entraTenantId: "<tenant-id>" # Filled in after step 1
governance-studio:
config:
authProvider: "entra"
entraTenantId: "<tenant-id>" # Filled in after step 1
entraClientId: "<spa-client-id>" # Filled in after step 5
entraScopes: "openid profile email offline_access api://<backend-client-id>/access_as_user" # Filled in after steps 2 & 5

secrets.yaml:

global:
secrets:
create: true
auth:
provider: "entra"
entra:
secretName: "platform-entra"
values:
clientId: "<backend-client-id>" # Filled in after step 2
clientSecret: "<backend-client-secret>" # Filled in after step 2
tenantId: "<tenant-id>" # Filled in after step 1
graphClientId: "<graph-client-id>" # Filled in after step 3
graphClientSecret: "<graph-client-secret>" # Filled in after step 3
governanceWorker:
secretName: "platform-governance-worker"
values:
encryptionKey: "<worker-encryption-key>" # Generate: openssl rand -base64 32
clientId: "<worker-client-id>" # Filled in after step 4
clientSecret: "<worker-client-secret>" # Filled in after step 4

The tenant ID can be found in Azure Portal > Microsoft Entra ID > Overview. Login to Azure with the tenant:

Terminal window
az login --tenant <tenant-id>

Verify the correct tenant:

Terminal window
az account show

Update values.yaml with the tenant ID.

values.yaml -> auth-service.config.idp:

issuer: "https://login.microsoftonline.com/<tenant-id>/v2.0"
entra:
tenantId: "<tenant-id>"

values.yaml -> governance-service.config:

entraTenantId: "<tenant-id>"

values.yaml -> governance-studio.config:

entraTenantId: "<tenant-id>"

Update secrets.yaml with the tenant ID.

secrets.yaml -> global.secrets.auth.entra.values:

tenantId: "<tenant-id>"

Create the backend API app registration. Replace <domain> with the governance platform domain (e.g. governance.example.com):

Terminal window
az ad app create \
--display-name "EQTY Governance Platform Backend" \
--sign-in-audience AzureADMyOrg \
--enable-id-token-issuance true \
--enable-access-token-issuance true \
--web-redirect-uris "https://<domain>/callback"

Save the appId and id from the output:

  • appId is the Client ID
  • id is the Object ID

Store both as variables for use in subsequent commands:

Terminal window
BACKEND_CLIENT_ID="<app-id-from-output>"
BACKEND_OBJECT_ID="<id-from-output>"

Create a service principal and set the application ID URI:

Terminal window
az ad sp create --id $BACKEND_CLIENT_ID
az ad app update --id $BACKEND_CLIENT_ID \
--identifier-uris "api://$BACKEND_CLIENT_ID"

Generate a unique ID for the API scope:

Terminal window
uuidgen

Save this UUID — it will be used as SCOPE_ID in the next step.

Create a file named backend-scope.json with the following content. Replace SCOPE_ID with the UUID from above:

{
"api": {
"requestedAccessTokenVersion": 2,
"oauth2PermissionScopes": [
{
"id": "SCOPE_ID",
"adminConsentDescription": "Allow the application to access EQTY Governance Platform on behalf of the signed-in user",
"adminConsentDisplayName": "Access EQTY Governance Platform",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allow the application to access EQTY Governance Platform on your behalf",
"userConsentDisplayName": "Access EQTY Governance Platform",
"value": "access_as_user"
}
]
}
}

Add the API scope to the app registration:

Terminal window
az rest --method PATCH \
--uri "https://graph.microsoft.com/v1.0/applications/$BACKEND_OBJECT_ID" \
--headers "Content-Type=application/json" \
--body @backend-scope.json

Create a client secret for the backend app:

Terminal window
az ad app credential reset \
--id $BACKEND_CLIENT_ID \
--append \
--query password \
--output tsv

Save the client secret — it cannot be retrieved later.

Update secrets.yaml with the backend credentials.

secrets.yaml -> global.secrets.auth.entra.values:

clientId: "<backend-client-id>"
clientSecret: "<client-secret-from-output>"

Update values.yaml with the backend client ID (used in the scopes string).

values.yaml -> governance-studio.config:

entraScopes: "openid profile email offline_access api://<backend-client-id>/access_as_user"

3. Create Graph API Access App Registration

Section titled “3. Create Graph API Access App Registration”

Create the Graph API access app registration:

Terminal window
az ad app create \
--display-name "EQTY Governance Platform Graph API" \
--sign-in-audience AzureADMyOrg

Save the appId from the output — this is the Graph API Client ID.

Store it as a variable for use in subsequent commands:

Terminal window
GRAPH_CLIENT_ID="<app-id-from-output>"

Create a service principal for the app:

Terminal window
az ad sp create --id $GRAPH_CLIENT_ID

Add Microsoft Graph API permissions for reading users and organization information:

Terminal window
az ad app permission add \
--id $GRAPH_CLIENT_ID \
--api 00000003-0000-0000-c000-000000000000 \
--api-permissions df021288-bdef-4463-88db-98f22de89214=Role 498476ce-e0fe-48b0-b801-37ba7e2685c6=Role

This adds:

  • User.Read.All (df021288-bdef-4463-88db-98f22de89214) – Read all users’ full profiles
  • Organization.Read.All (498476ce-e0fe-48b0-b801-37ba7e2685c6) – Read organization information

Grant admin consent for the API permissions (requires admin privileges):

Terminal window
az ad app permission admin-consent --id $GRAPH_CLIENT_ID

Create a client secret for the Graph API app:

Terminal window
az ad app credential reset \
--id $GRAPH_CLIENT_ID \
--append \
--query password \
--output tsv

Save the client secret — it cannot be retrieved later.

Update secrets.yaml with the Graph API credentials.

secrets.yaml -> global.secrets.auth.entra.values:

graphClientId: "<graph-client-id>"
graphClientSecret: "<client-secret-from-output>"

4. Create Governance Worker App Registration

Section titled “4. Create Governance Worker App Registration”

The Governance Worker is a confidential M2M (machine-to-machine) client used for automated governance workflow execution.

Create the Governance Worker app registration:

Terminal window
az ad app create \
--display-name "EQTY Governance Worker" \
--sign-in-audience AzureADMyOrg

Save the appId from the output — this is the Worker Client ID.

Store it as a variable for use in subsequent commands:

Terminal window
WORKER_CLIENT_ID="<app-id-from-output>"

Create a service principal for the app:

Terminal window
az ad sp create --id $WORKER_CLIENT_ID

Create a client secret for the Worker app:

Terminal window
az ad app credential reset \
--id $WORKER_CLIENT_ID \
--append \
--query password \
--output tsv

Save the client secret — it cannot be retrieved later.

Generate an encryption key for the worker:

Terminal window
openssl rand -base64 32

Update secrets.yaml with the worker credentials.

secrets.yaml -> global.secrets.governanceWorker.values:

encryptionKey: "<generated-key>"
clientId: "<worker-client-id>"
clientSecret: "<client-secret-from-output>"

Create the SPA app registration:

Terminal window
az ad app create \
--display-name "EQTY Governance Studio" \
--sign-in-audience AzureADMyOrg \
--enable-id-token-issuance true \
--enable-access-token-issuance true

Save the appId and id from the output:

  • appId is the SPA Client ID
  • id is the SPA Object ID

Store both as variables:

Terminal window
SPA_CLIENT_ID="<app-id-from-output>"
SPA_OBJECT_ID="<id-from-output>"

Configure SPA redirect URIs and set token version to v2.0. Replace <domain> with the governance platform domain:

Terminal window
az rest --method PATCH \
--uri "https://graph.microsoft.com/v1.0/applications/$SPA_OBJECT_ID" \
--headers "Content-Type=application/json" \
--body "{\"spa\":{\"redirectUris\":[\"https://<domain>\"]},\"api\":{\"requestedAccessTokenVersion\":2}}"

Create a service principal for the SPA app:

Terminal window
az ad sp create --id $SPA_CLIENT_ID

Get the backend API scope ID:

Terminal window
az ad app show --id $BACKEND_CLIENT_ID --query 'api.oauth2PermissionScopes[0].id' -o tsv

Save this scope ID — it will be used in the next command.

Grant the SPA access to the backend API. Replace <scope-id> with the value from above:

Terminal window
az ad app permission add \
--id $SPA_CLIENT_ID \
--api $BACKEND_CLIENT_ID \
--api-permissions <scope-id>=Scope

Optionally, grant admin consent for the API permission:

Terminal window
az ad app permission admin-consent --id $SPA_CLIENT_ID

Note: Admin consent may require higher privileges. If this fails, grant consent later through the Azure Portal.

Update values.yaml with the SPA client ID.

values.yaml -> governance-studio.config:

entraClientId: "<spa-client-id>"
Click to expand Azure Portal instructions
  • Navigate to Azure Portal > Microsoft Entra ID > App registrations
  • Click “New registration”
  • Name: “EQTY Governance Platform Backend”
  • Supported account types: “Accounts in this organizational directory only”
  • Redirect URI: Web, https://<domain>/callback
  • Click “Register”
  • Note the Application (client) ID and Directory (tenant) ID
  • Navigate to “Manifest”
  • Find “accessTokenAcceptedVersion” and set it to 2 (for v2.0 tokens)
  • Click “Save”
  • Navigate to “Expose an API”
  • Set Application ID URI: api://<client-id>
  • Add a scope:
    • Scope name: access_as_user
    • Admin consent display name: “Access EQTY Governance Platform”
    • Admin consent description: “Allow the application to access EQTY Governance Platform on behalf of the signed-in user”
    • State: Enabled
    • Click “Add scope”
  • Navigate to “Certificates & secrets”
  • Click “New client secret”
  • Description: “Governance Platform Secret”
  • Expires: 24 months (or the organization’s policy)
  • Click “Add”
  • Copy the secret Value immediately — it won’t be shown again

Update values.yaml with the tenant ID and backend credentials.

values.yaml -> auth-service.config.idp:

issuer: "https://login.microsoftonline.com/<tenant-id>/v2.0"
entra:
tenantId: "<tenant-id>"

values.yaml -> governance-service.config:

entraTenantId: "<tenant-id>"

values.yaml -> governance-studio.config:

entraTenantId: "<tenant-id>"
entraScopes: "openid profile email offline_access api://<backend-client-id>/access_as_user"

Update secrets.yaml with the backend credentials.

secrets.yaml -> global.secrets.auth.entra.values:

clientId: "<backend-client-id>"
clientSecret: "<client-secret>"
tenantId: "<tenant-id>"

2. Create Graph API Access App Registration

Section titled “2. Create Graph API Access App Registration”
  • Create another app registration: “EQTY Governance Platform Graph API”
  • Navigate to “API permissions”
  • Click “Add a permission”
  • Select “Microsoft Graph” > “Application permissions”
  • Add: User.Read.All, Organization.Read.All
  • Click “Add permissions”
  • Click “Grant admin consent” (requires admin role)
  • Navigate to “Certificates & secrets”
  • Create a client secret
  • Save the secret value

Update secrets.yaml with the Graph API credentials.

secrets.yaml -> global.secrets.auth.entra.values:

graphClientId: "<graph-client-id>"
graphClientSecret: "<graph-client-secret>"

3. Create Governance Worker App Registration

Section titled “3. Create Governance Worker App Registration”
  • Create app registration: “EQTY Governance Worker”
  • Supported account types: “Accounts in this organizational directory only”
  • No redirect URI needed (M2M client)
  • Click “Register”
  • Navigate to “Certificates & secrets”
  • Create a client secret
  • Save the secret value

Generate an encryption key for the worker:

Terminal window
openssl rand -base64 32

Update secrets.yaml with the worker credentials.

secrets.yaml -> global.secrets.governanceWorker.values:

encryptionKey: "<generated-key>"
clientId: "<worker-client-id>"
clientSecret: "<worker-client-secret>"
  • Create app registration: “EQTY Governance Studio”
  • Navigate to “Manifest”
  • Find “accessTokenAcceptedVersion” and set it to 2 (for v2.0 tokens)
  • Click “Save”
  • Navigate to “Authentication”
  • Click “Add a platform” > “Single-page application”
  • Redirect URIs: https://<domain>
  • Check “ID tokens” under Implicit grant and hybrid flows
  • Check “Access tokens” under Implicit grant and hybrid flows
  • Click “Configure”
  • Navigate to “API permissions”
  • Click “Add a permission”
  • Select “My APIs”
  • Select “EQTY Governance Platform Backend”
  • Check the access_as_user scope
  • Click “Add permissions”
  • Optional: Click “Grant admin consent”

Update values.yaml with the SPA client ID.

values.yaml -> governance-studio.config:

entraClientId: "<spa-client-id>"

Post-Installation: Create Organization and Admin User

Section titled “Post-Installation: Create Organization and Admin User”

After deploying the platform, create an organization and admin user:

# Add to values.yaml
entra:
createOrganization: true
organizationName: "your-org-name"
displayName: "Organization Display Name"
createPlatformAdmin: true
platformAdminEmail: "admin@example.onmicrosoft.com" # Must exist in Entra
tenantId: "<tenant-id>"

This will:

  • Create an organization in the governance database
  • Look up the Entra user by email via Graph API
  • Create a platform-admin user record linked to the Entra user

Verify the OIDC discovery endpoint:

Terminal window
curl "https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration"

Test token issuance with backend API credentials:

Terminal window
curl -X POST "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=<backend-client-id>" \
-d "client_secret=<backend-client-secret>" \
-d "scope=api://<backend-client-id>/.default" \
-d "grant_type=client_credentials"

Test Graph API access:

Terminal window
curl -X POST "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=<graph-client-id>" \
-d "client_secret=<graph-client-secret>" \
-d "scope=https://graph.microsoft.com/.default" \
-d "grant_type=client_credentials"

If both commands succeed and return access tokens, the Entra configuration is correct.

  • Use Certificate Authentication: For production, consider using certificates instead of client secrets
  • Rotate Secrets: Regularly rotate client secrets (recommended: every 6-12 months)
  • Conditional Access: Implement Conditional Access policies for enhanced security
  • Multi-Factor Authentication: Require MFA for all users
  • Least Privilege: Only grant the minimum required Graph API permissions
  • Monitor Sign-ins: Enable sign-in logs and monitor for suspicious activity
  • App Consent Policies: Configure app consent policies to control which apps users can consent to
  • “AADSTS65001: The user or administrator has not consented to use the application” — Solution: Grant admin consent for API permissions in the Azure Portal
  • “AADSTS700016: Application not found in the directory” — Solution: Verify the client ID is correct and the app registration exists in the tenant
  • “Invalid scope” — Solution: Ensure the entraScopes configuration includes api://<backend-client-id>/access_as_user
  • Graph API returns 403 Forbidden — Solution: Verify admin consent was granted for Graph API permissions