Blockstream Enterprise
Recipes

Microsoft Entra ID Setup Guide

This guide covers the complete setup of Microsoft Entra ID as an identity provider for the Custody Engine.

Prerequisites

  • Access to Microsoft Azure Portal with permissions to create App Registrations
  • Azure AD PowerShell module or Microsoft Graph PowerShell SDK installed
  • Tenant ID of your Azure AD directory

1. Register the Application

1.1 Create App Registration

  1. Navigate to Azure PortalMicrosoft Entra IDApp registrations
  2. Click New registration
  3. Configure the following:
    • Name: Custody Desktop (or your preferred name)
    • Supported account types: Select based on your requirements
    • Redirect URI:
      • Platform: Public client/native (mobile & desktop)
      • URI: ecs://oauth-response
  4. Click Register

1.2 Note Required Values

After registration, note the following values from the Overview page:

ValueLocationExample
Application (client) IDOverview12345678-1234-1234-1234-123456789abc
Directory (tenant) IDOverview87654321-4321-4321-4321-cba987654321
Object IDOverviewabcdef12-3456-7890-abcd-ef1234567890

2. Configure Authentication

2.1 Platform Configuration

  1. Go to Authentication in your app registration
  2. Under Platform configurations, verify or add:
    • Platform: Mobile and desktop applications
    • Custom redirect URI: ecs://oauth-response
  3. Under Advanced settings:
    • Enable Allow public client flows → Set to Yes
  4. Click Save

2.2 Enable PKCE

The application uses PKCE (Proof Key for Code Exchange) for secure authorization. This is automatically supported when using the public client flow with custom redirect URIs.

3. Configure API Permissions

  1. Go to API permissions
  2. Click Add a permissionMicrosoft GraphDelegated permissions
  3. Add the following permissions:
    • openid (required for OIDC)
    • profile (for name claims)
    • email (for email claim)
  4. Click Add permissions
  5. If required by your organization, click Grant admin consent for [Organization]

4. Configure Claims

The application requires both standard and custom claims in the JWT tokens.

4.1 Optional Claims (Standard Claims)

  1. Go to Token configuration
  2. Click Add optional claim
  3. Select ID token type and add:
    • email
    • given_name
    • family_name
  4. Repeat for Access token type with the same claims
  5. Click Add

Alternatively, edit the Manifest directly and add:

"optionalClaims": {
  "idToken": [
    {
      "name": "email",
      "source": null,
      "essential": true,
      "additionalProperties": []
    },
    {
      "name": "given_name",
      "source": null,
      "essential": false,
      "additionalProperties": []
    },
    {
      "name": "family_name",
      "source": null,
      "essential": false,
      "additionalProperties": []
    }
  ],
  "accessToken": [
    {
      "name": "email",
      "source": null,
      "essential": true,
      "additionalProperties": []
    },
    {
      "name": "given_name",
      "source": null,
      "essential": false,
      "additionalProperties": []
    },
    {
      "name": "family_name",
      "source": null,
      "essential": false,
      "additionalProperties": []
    }
  ],
  "saml2Token": []
}

4.2 Custom Claims (Server Keys)

The application requires custom claims for server public keys:

  • srv_enc_pub - Server RSA public key for encryption
  • srv_sig_pub - Server ECDSA public key for signatures

4.2.1 Create Extension Attributes

Using Azure AD PowerShell:

# Connect to Azure AD
Connect-AzureAD

# Get your application object ID
$appObjectId = "YOUR_APP_OBJECT_ID"

# Create srv_enc_pub extension attribute
New-AzureADApplicationExtensionProperty `
  -ObjectId $appObjectId `
  -Name "srv_enc_pub" `
  -DataType "String" `
  -TargetObjects "User"

# Create srv_sig_pub extension attribute
New-AzureADApplicationExtensionProperty `
  -ObjectId $appObjectId `
  -Name "srv_sig_pub" `
  -DataType "String" `
  -TargetObjects "User"

Using Microsoft Graph PowerShell SDK:

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Application.ReadWrite.All"

# Create extension properties
$params = @{
  name = "srv_enc_pub"
  dataType = "String"
  targetObjects = @("User")
}
New-MgApplicationExtensionProperty -ApplicationId $appObjectId -BodyParameter $params

$params = @{
  name = "srv_sig_pub"
  dataType = "String"
  targetObjects = @("User")
}
New-MgApplicationExtensionProperty -ApplicationId $appObjectId -BodyParameter $params

4.2.2 Create Claims Mapping Policy

# Replace {app-id-no-dashes} with your Application ID without dashes
# Example: 12345678-1234-1234-1234-123456789abc becomes 1234567812341234123456789abc

$policyDefinition = @"
{
  "ClaimsMappingPolicy": {
    "Version": 1,
    "IncludeBasicClaimSet": "true",
    "ClaimsSchema": [
      {
        "Source": "user",
        "ExtensionID": "extension_{app-id-no-dashes}_srv_enc_pub",
        "JwtClaimType": "srv_enc_pub"
      },
      {
        "Source": "user",
        "ExtensionID": "extension_{app-id-no-dashes}_srv_sig_pub",
        "JwtClaimType": "srv_sig_pub"
      }
    ]
  }
}
"@

# Create the policy
$policy = New-AzureADPolicy `
  -Definition $policyDefinition `
  -DisplayName "CustodyClaimsPolicy" `
  -Type "ClaimsMappingPolicy"

# Get the service principal for your application
$servicePrincipal = Get-AzureADServicePrincipal -Filter "AppId eq 'YOUR_APPLICATION_CLIENT_ID'"

# Assign the policy to the service principal
Add-AzureADServicePrincipalPolicy `
  -Id $servicePrincipal.ObjectId `
  -RefObjectId $policy.Id

4.2.3 Enable Claims Mapping in Manifest

  1. Go to Manifest in your app registration
  2. Set acceptMappedClaims to true:
"acceptMappedClaims": true
  1. Click Save

Note: If you receive an error about acceptMappedClaims, you may need to configure a custom signing key. See Microsoft Documentation for details.

4.3 Set User Extension Attribute Values

For each user that will authenticate, set the server key values:

# Get the user
$user = Get-AzureADUser -ObjectId "user@domain.com"

# Set extension attribute values
# Replace {app-id-no-dashes} with your Application ID without dashes
Set-AzureADUserExtension -ObjectId $user.ObjectId -ExtensionName "extension_{app-id-no-dashes}_srv_enc_pub" -ExtensionValue "YOUR_RSA_PUBLIC_KEY"
Set-AzureADUserExtension -ObjectId $user.ObjectId -ExtensionName "extension_{app-id-no-dashes}_srv_sig_pub" -ExtensionValue "YOUR_ECDSA_PUBLIC_KEY"

5. Application Configuration

For the application, we require this data:

issuer = "https://login.microsoftonline.com/{tenant-id}/v2.0"
audience = "{client-id}"
jwks_url = "https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys"

Replace:

  • {tenant-id} with your Directory (tenant) ID
  • {client-id} with your Application (client) ID

6. Entra ID Endpoints Reference

PurposeURL
Authorizationhttps://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize
Tokenhttps://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
OIDC Discoveryhttps://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration
JWKShttps://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys
Logouthttps://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/logout

7. Verification (optional)

7.1 Test Token Acquisition

Use the OAuth 2.0 authorization code flow with PKCE to obtain a token:

  1. Generate a code verifier and code challenge
  2. Direct user to authorization endpoint with:
    • client_id
    • redirect_uri=ecs://oauth-response
    • scope=openid profile email
    • response_type=code
    • code_challenge and code_challenge_method=S256
  3. Exchange the authorization code for tokens at the token endpoint

7.2 Verify Token Claims

Decode the ID token (using jwt.ms or similar) and verify it contains:

  • Standard claims: email, given_name, family_name
  • Custom claims: srv_enc_pub, srv_sig_pub
  • Correct iss (issuer) and aud (audience) values

8. Troubleshooting

Common Issues

IssueSolution
AADSTS50011: Reply URL mismatchVerify ecs://oauth-response is configured as a redirect URI
AADSTS7000218: Request body must contain client_assertionEnable "Allow public client flows" in Authentication settings
Custom claims not appearingVerify claims mapping policy is assigned to service principal
acceptMappedClaims errorConfigure a custom signing key or use a verified domain
Extension attributes not foundEnsure extension properties are created on the correct application

Useful Commands

# List extension properties for an application
Get-AzureADApplicationExtensionProperty -ObjectId $appObjectId

# List claims mapping policies
Get-AzureADPolicy -All $true | Where-Object { $_.Type -eq "ClaimsMappingPolicy" }

# List policies assigned to a service principal
Get-AzureADServicePrincipalPolicy -Id $servicePrincipal.ObjectId

# Remove a claims mapping policy from service principal
Remove-AzureADServicePrincipalPolicy -Id $servicePrincipal.ObjectId -PolicyId $policy.Id

On this page