Skip to content

Application Migration: LDAP and Kerberos to Modern Authentication

Technical guide for migrating on-premises applications from LDAP, Kerberos, NTLM, and AD FS authentication to modern authentication patterns with Microsoft Entra ID --- covering App Proxy, SAML/OIDC migration, Graph API, and Kerberos cloud trust.


Overview

Application migration is the most complex and time-consuming aspect of AD-to-Entra-ID migration. Every application that authenticates against Active Directory --- whether through LDAP bind, Kerberos ticket, NTLM challenge, or AD FS federation --- must be assessed, categorized, and migrated to modern authentication or bridged through a compatibility layer.

This guide provides the technical patterns for each migration path, prioritization frameworks, and CSA-in-a-Box integration patterns.


1. Application inventory and assessment

Step 1: Discover AD-dependent applications

# Method 1: AD FS relying party trusts
# Run on AD FS server:
Get-AdfsRelyingPartyTrust |
    Select-Object Name, Identifier, IssuanceTransformRules,
    MonitoringEnabled, Enabled, TokenLifetime |
    Export-Csv ".\adfs-relying-parties.csv" -NoTypeInformation

# Method 2: LDAP query logging
# Enable LDAP diagnostics on domain controllers:
# Registry: HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics
# Set "16 LDAP Interface Events" to 2 (Basic)
# Analyze Event ID 2889 (unsigned LDAP binds) and 1644 (LDAP queries)

# Method 3: Kerberos service ticket analysis
# Analyze Security Event 4769 (Kerberos Service Ticket Operations)
Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    Id = 4769
    StartTime = (Get-Date).AddDays(-30)
} | ForEach-Object {
    $xml = [xml]$_.ToXml()
    [PSCustomObject]@{
        ServiceName = $xml.Event.EventData.Data[0].'#text'
        ClientAddress = $xml.Event.EventData.Data[6].'#text'
        TicketType = $xml.Event.EventData.Data[4].'#text'
    }
} | Group-Object ServiceName | Sort-Object Count -Descending |
    Select-Object Count, Name | Export-Csv ".\kerberos-service-tickets.csv"

Step 2: Categorize applications

Category Auth protocol Migration path Complexity Priority
A: SaaS apps on AD FS SAML/WS-Fed via AD FS Entra ID SSO (gallery or custom) Low--Medium 1 (first)
B: Modern web apps OAuth/OIDC MSAL SDK migration Low 2
C: Legacy web apps (IIS) Windows Integrated Auth Entra Application Proxy Medium 3
D: LDAP-bound apps LDAP simple/secure bind Graph API migration or Entra Domain Services High 4
E: Kerberos apps Kerberos constrained delegation App Proxy KCD or cloud trust High 5
F: NTLM-only apps NTLM challenge/response App rewrite or retirement Very High 6 (last)

2. Category A: AD FS to Entra ID SSO migration

AD FS application activity report

# Generate AD FS application usage report
# Identifies which relying parties are actively used

# On AD FS server (requires AD FS 2019+):
$rpUsage = Get-AdfsRelyingPartyTrust | ForEach-Object {
    $rp = $_
    $events = Get-WinEvent -FilterHashtable @{
        LogName = 'AD FS/Admin'
        Id = 299  # Token issued
        StartTime = (Get-Date).AddDays(-90)
    } -ErrorAction SilentlyContinue |
    Where-Object { $_.Message -match $rp.Identifier }

    [PSCustomObject]@{
        Name = $rp.Name
        Identifier = $rp.Identifier[0]
        Protocol = if ($rp.WSFedEndpoint) { "WS-Fed" } else { "SAML" }
        TokensIssued90Days = $events.Count
        LastUsed = ($events | Sort-Object TimeCreated -Descending | Select-Object -First 1).TimeCreated
    }
}

$rpUsage | Sort-Object TokensIssued90Days -Descending |
    Export-Csv ".\adfs-app-usage.csv" -NoTypeInformation

Migration process: AD FS to Entra SSO

flowchart TD
    A[1. Check Entra gallery\nfor pre-integrated app] --> B{App in gallery?}
    B -->|Yes| C[2. Configure gallery app\nOne-click SSO setup]
    B -->|No| D[2. Create custom\nenterprise app]
    C --> E[3. Configure SAML/OIDC\nclaims and attributes]
    D --> E
    E --> F[4. Test SSO with\npilot users]
    F --> G[5. Cut over DNS/URL\nfrom AD FS to Entra]
    G --> H[6. Remove AD FS\nrelying party trust]

SAML claim mapping: AD FS to Entra

AD FS claim rule Entra ID equivalent Configuration
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"] User.userprincipalname (default) Automatic
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] User.mail Attribute mapping
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups"] User.groups Group claims configuration
Custom claim: department User.department Custom claim mapping
Custom claim: employee ID User.employeeid Custom claim mapping
Transform rule (regex) Claims transformation policy Entra ID claim transformation
# Configure SAML claims for enterprise app
$appObjectId = "enterprise-app-object-id"

# Add custom claim mapping policy
$claimPolicy = @{
    definition = @(
        '{"ClaimsMappingPolicy":{"Version":1,"IncludeBasicClaimSet":"true","ClaimsSchema":[{"Source":"user","ID":"employeeid","SamlClaimType":"http://schemas.contoso.com/claims/employeeid"},{"Source":"user","ID":"department","SamlClaimType":"http://schemas.contoso.com/claims/department"}]}}'
    )
    displayName = "Custom Claims - Contoso App"
    isOrganizationDefault = $false
}

$policy = New-MgPolicyClaimMappingPolicy -BodyParameter $claimPolicy
# Assign policy to service principal

3. Category B: Modern web application migration

ADAL to MSAL migration

ADAL (Active Directory Authentication Library) is deprecated. Applications using ADAL must migrate to MSAL (Microsoft Authentication Library).

// BEFORE: ADAL (deprecated)
var authContext = new AuthenticationContext(
    "https://login.microsoftonline.com/contoso.com");
var result = await authContext.AcquireTokenAsync(
    "https://graph.microsoft.com",
    clientId,
    new UserPasswordCredential(username, password));

// AFTER: MSAL
var app = ConfidentialClientApplicationBuilder
    .Create(clientId)
    .WithAuthority(AzureCloudInstance.AzurePublic, tenantId)
    .WithClientSecret(clientSecret) // or .WithCertificate()
    .Build();

var result = await app.AcquireTokenForClient(
    new[] { "https://graph.microsoft.com/.default" })
    .ExecuteAsync();
# BEFORE: ADAL (deprecated)
import adal
context = adal.AuthenticationContext(
    f"https://login.microsoftonline.com/{tenant_id}")
token = context.acquire_token_with_client_credentials(
    "https://graph.microsoft.com", client_id, client_secret)

# AFTER: MSAL
from msal import ConfidentialClientApplication
app = ConfidentialClientApplication(
    client_id,
    authority=f"https://login.microsoftonline.com/{tenant_id}",
    client_credential=client_secret)

token = app.acquire_token_for_client(
    scopes=["https://graph.microsoft.com/.default"])
// BEFORE: ADAL.js (deprecated)
var authContext = new AuthenticationContext({
    clientId: clientId,
    tenant: tenantId,
});

// AFTER: MSAL.js
import { PublicClientApplication } from "@azure/msal-browser";

const msalConfig = {
    auth: {
        clientId: clientId,
        authority: `https://login.microsoftonline.com/${tenantId}`,
        redirectUri: "https://app.contoso.com",
    },
};

const msalInstance = new PublicClientApplication(msalConfig);
const loginResponse = await msalInstance.loginPopup({
    scopes: ["User.Read"],
});

4. Category C: Legacy web applications (Application Proxy)

Entra Application Proxy provides secure remote access to on-premises web applications without VPN, with Entra SSO and Conditional Access.

Application Proxy architecture

flowchart LR
    User[Remote User] -->|HTTPS| EntraID[Entra ID\nConditional Access]
    EntraID -->|Token| AppProxy[Entra App Proxy\nCloud Service]
    AppProxy -->|Outbound HTTPS| Connector[App Proxy Connector\nOn-premises]
    Connector -->|Kerberos/WIA/NTLM| LegacyApp[Legacy Web App\nIIS / Apache]

    style EntraID fill:#1565c0,color:#fff
    style AppProxy fill:#1565c0,color:#fff
    style Connector fill:#f57c00,color:#fff

Deploy Application Proxy

# Step 1: Install Application Proxy connector on an on-premises server
# Download from Entra admin center > Applications > Application Proxy
# Run installer:
.\AADApplicationProxyConnectorInstaller.exe /quiet

# Step 2: Configure the enterprise application
# Via Microsoft Graph API:
$appProxy = @{
    displayName = "Legacy HR Application"
    web = @{
        redirectUris = @("https://hr.contoso.com/")
    }
}
$app = New-MgApplication -BodyParameter $appProxy

# Step 3: Configure Application Proxy settings
# Entra admin center > Enterprise Apps > [App] > Application Proxy
# Internal URL: http://hr-internal.contoso.local:8080/
# External URL: https://hr.contoso.com/ (auto-generated or custom domain)
# Pre-authentication: Azure Active Directory
# Connector Group: Default (or custom group)

Single sign-on with Kerberos constrained delegation

# Configure KCD for the Application Proxy connector
# The connector machine account needs delegation rights

# Step 1: Set SPN on the application service account
setspn -s HTTP/hr-internal.contoso.local svc-hr-app

# Step 2: Configure KCD on the connector computer account
# Active Directory Users and Computers > Connector Computer > Delegation tab
# "Trust this computer for delegation to specified services only"
# "Use any authentication protocol"
# Add: HTTP/hr-internal.contoso.local

# Step 3: Configure SSO in the enterprise app
# Entra admin center > Enterprise Apps > [App] > Single sign-on
# Mode: Windows Integrated Authentication
# Internal application SPN: HTTP/hr-internal.contoso.local
# Delegated Login Identity: User principal name

5. Category D: LDAP application migration

LDAP to Microsoft Graph API migration

LDAP operation Graph API equivalent Example
ldap_search(base, scope, filter) GET /users?$filter=... GET /users?$filter=department eq 'Finance'
ldap_bind(dn, password) OAuth token acquisition MSAL AcquireTokenByUsernamePassword()
ldap_compare(dn, attr, value) GET /users/{id}?$select=attr Check attribute value via GET
ldap_modify(dn, changes) PATCH /users/{id} Update user attributes
ldap_add(dn, attrs) POST /users Create new user
ldap_delete(dn) DELETE /users/{id} Delete user
ldap_search with memberOf GET /users/{id}/memberOf Group membership query

LDAP query migration examples

# BEFORE: LDAP query
import ldap

conn = ldap.initialize("ldap://dc01.contoso.com")
conn.simple_bind_s("CN=svc-app,OU=Service,DC=contoso,DC=com", "password")

results = conn.search_s(
    "OU=Users,DC=contoso,DC=com",
    ldap.SCOPE_SUBTREE,
    "(department=Finance)",
    ["displayName", "mail", "memberOf"]
)

for dn, attrs in results:
    print(f"Name: {attrs['displayName'][0]}, Email: {attrs['mail'][0]}")

# AFTER: Microsoft Graph API
from azure.identity import ClientSecretCredential
from msgraph import GraphServiceClient

credential = ClientSecretCredential(tenant_id, client_id, client_secret)
client = GraphServiceClient(credential)

users = await client.users.get(
    request_configuration=RequestConfiguration(
        query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
            filter="department eq 'Finance'",
            select=["displayName", "mail"],
            expand=["memberOf"]
        )
    )
)

for user in users.value:
    print(f"Name: {user.display_name}, Email: {user.mail}")

When to use Entra Domain Services instead

Entra Domain Services (Entra DS) provides a managed LDAP endpoint in the cloud. Use it when:

  • Application source code is not available for modification
  • Application requires LDAP bind and cannot be rewritten
  • Application uses LDAP search extensively and Graph API migration is impractical
  • Timeline does not allow application remediation
// Deploy Entra Domain Services via Bicep
resource aadds 'Microsoft.AAD/DomainServices@2022-12-01' = {
  name: 'contoso.com'
  location: location
  properties: {
    domainName: 'aadds.contoso.com'
    filteredSync: 'Enabled'  // Only sync specific groups
    sku: 'Standard'
    ldapsSettings: {
      ldaps: 'Enabled'
      externalAccess: 'Disabled'  // Internal only
    }
    notificationSettings: {
      notifyDcAdmins: 'Enabled'
      notifyGlobalAdmins: 'Enabled'
    }
  }
}

6. Category E: Kerberos application migration

Kerberos cloud trust

Kerberos cloud trust enables Entra-joined devices to obtain Kerberos tickets for on-premises resources without line of sight to a domain controller.

# Configure Kerberos cloud trust
# Step 1: Create the Entra Kerberos server object in AD
Install-Module -Name AzureADHybridAuthenticationManagement -Force

$domain = "contoso.com"
$cloudCred = Get-Credential -Message "Entra ID Global Admin"
$onPremCred = Get-Credential -Message "AD Domain Admin"

Set-AzureADKerberosServer -Domain $domain `
    -CloudCredential $cloudCred `
    -DomainCredential $onPremCred

# Step 2: Verify the Kerberos server object
Get-AzureADKerberosServer -Domain $domain -CloudCredential $cloudCred `
    -DomainCredential $onPremCred

7. Application migration tracking

Migration status dashboard

Application Category Protocol Migration path Status Target date
Salesforce A SAML Entra SSO gallery Complete ---
ServiceNow A SAML Entra SSO gallery Complete ---
Custom HR Portal C WIA App Proxy + KCD In progress Week 12
Legacy ERP D LDAP Graph API rewrite Planning Week 20
File Server E Kerberos Cloud trust In progress Week 16
Old Intranet F NTLM Retirement Approved Week 24

Migration decision tree

flowchart TD
    A[Application uses AD auth] --> B{What protocol?}
    B -->|SAML/WS-Fed via AD FS| C[Migrate to Entra SSO]
    B -->|OAuth/OIDC| D[Update to MSAL]
    B -->|Windows Integrated Auth| E{Source code available?}
    B -->|LDAP| F{Source code available?}
    B -->|NTLM only| G[Retire or rewrite]

    E -->|Yes| H[Modernize to OIDC]
    E -->|No| I[Deploy App Proxy]

    F -->|Yes| J[Migrate to Graph API]
    F -->|No| K[Deploy Entra Domain Services]

    style C fill:#388e3c,color:#fff
    style D fill:#388e3c,color:#fff
    style H fill:#388e3c,color:#fff
    style I fill:#f57c00,color:#fff
    style J fill:#388e3c,color:#fff
    style K fill:#f57c00,color:#fff
    style G fill:#d32f2f,color:#fff

CSA-in-a-Box integration

Application migration enables full CSA-in-a-Box platform access through Entra ID:

  • Databricks workspace: OAuth/OIDC SSO via Entra ID; no AD FS dependency
  • Fabric portal: Entra SSO with Conditional Access; no legacy auth
  • Purview catalog: Graph API for programmatic access; no LDAP
  • Power BI embedded: MSAL token acquisition for embedded analytics
  • Custom data applications: MSAL SDK for all new application development

All CSA-in-a-Box applications use modern authentication exclusively. Legacy auth protocols are not supported.


Maintainers: csa-inabox core team Last updated: 2026-04-30