The Problem That Doesn't Exist Until It Does: Connecting Cloud Services to an NTLM-Only CRM
Inspired by Aylin generated by @Gemini

The Problem That Doesn't Exist Until It Does: Connecting Cloud Services to an NTLM-Only CRM

TL;DR: Blocked by a legacy Dynamics CRM on-premise system that only speaks NTLM? I built a 90-line C# "Identity Proxy" that translates modern Azure OAuth/Managed Identities into NTLM handshakes. It unblocked workstreams without waiting for a Dataverse migration or an AD FS retrofit.

I've been delivering transformation programmes for a very long while now. You see all kinds of challenges — data migrations that uncover decades of inconsistency, stakeholder alignment that takes longer than the build, platform decisions that get revisited three times before anyone commits. These kinds of challenges in transformation projects can be more than you'd imagine. After enough programmes, most of them feel familiar. You recognise the shape of the problem early and you know the playbook.

But every now and then you hit one that sits in a blind spot. Not the kind of problem you can plan for perfectly upfront — it's the kind that only becomes real when you're face to face with it. And when it hits, it is the challenge.

This is one of those. I hope it sheds a light for someone else out there facing the same thing — and I have to say, I've seen this challenge more than once! 😃

The Setup

We're in the middle of a multi-stream digital transformation programme. Multiple workstreams running in parallel — some modernising business processes, others migrating platforms, others building entirely new capabilities on Azure. The target state includes Dynamics 365 Online (Dataverse), but that migration is its own complex workstream with its own timeline.

Several of the other streams were ready to go to production. Cloud-native workloads, built on Azure, ready to ship. But every one of them needed to talk to the system of record — and that system was Dynamics CRM On-Premises.

Now, CRM on-prem isn't unusual. What made this one interesting is how it was configured: pure NTLM authentication, sitting in a closed network. No AD FS. No claims-based authentication. No federation. No external exposure whatsoever. It was stood up years ago for internal use with IIS and Windows Authentication, and there was simply never a reason to expose it through a federated identity provider.

Without AD FS, there's no OAuth token endpoint for CRM. No way for any external service to authenticate using modern protocols. The only way in is NTLM with domain credentials over a direct connection.

The Bottleneck

Here's where it becomes a programme-level problem rather than just a technical one.

The workstreams building on Azure — Logic Apps, Power Automate, custom APIs — all authenticate with managed identities and OAuth tokens. That's the modern pattern. That's what Azure services do natively. None of them have any mechanism to perform an NTLM handshake against a server in a closed network.

So you've got multiple streams, ready to ship, blocked on one integration point. Waiting for Dataverse would stall them indefinitely — that migration has its own dependencies and timeline. Retrofitting AD FS onto the existing CRM deployment would unblock things eventually, but that's its own project with its own risks and its own timeline. Neither option was acceptable when teams were ready to go.

I've seen this pattern before in other programmes — a single legacy dependency becoming a chokepoint that quietly holds up everything downstream. The trick is recognising it early and finding the smallest intervention that releases the pressure.

Looking for an Existing Solution

Before building anything, I went through every Azure service that could conceivably bridge this gap. I've worked with most of them across different programmes, so I had a fair idea of what they could and couldn't do, but I wanted to be thorough.

Azure API Management — great at OAuth validation and policy enforcement on the frontend, but it can't inject NTLM credentials into backend connections. NTLM requires connection affinity across a multi-step handshake, and APIM's connection model doesn't support that.

Azure Application Gateway — it can pass through NTLM traffic with session affinity, but it can't inject credentials it doesn't have. It's a load balancer, not an authentication translator.

Microsoft Entra Application Proxy — this was the most promising candidate. It's designed for exactly this pattern: publish an on-premises app with Entra ID pre-authentication and handle Windows Integrated Authentication to the backend. But it relies on Kerberos Constrained Delegation, not pure NTLM. It also needs domain-joined connector agents and is oriented toward interactive user flows — not service-to-service communication like a Logic App calling via managed identity. Close, but not quite.

Azure Front Door, Azure Relay, Hybrid Connections — none of these handle authentication transformation.

Nothing fit. It's a genuinely specific gap — OAuth on one side, NTLM on the other, no federation layer in between, and the callers are automated services, not users in browsers.

The Solution

Rather than waiting for Dataverse or retrofitting AD FS, we built a purpose-built proxy. About 90 lines of C#. A minimal API that does exactly one thing: accept requests authenticated with OAuth via Entra ID, strip the token, forward the request to CRM with NTLM domain credentials injected, and return CRM's response as-is.

A Logic App sends a request with a managed identity token. The proxy handles the NTLM handshake with CRM. The Logic App gets back exactly what CRM returned — status codes, headers, body, errors. The experience is identical to calling CRM directly. The proxy is invisible, but highly observable — leveraging our standard infrastructure patterns for automated health tracking and horizontal scaling to handle NTLM handshake overhead effortlessly.

This is what made it interesting to me from a programme perspective: it's not just a technical fix, it's a strategic one. Every workstream that needs CRM access builds against the proxy using modern authentication patterns. They write their Logic Apps, their APIs, their integrations the same way they would against Dataverse. When Dataverse eventually goes live, they change the endpoint. No rework. No throwaway integrations. The contracts stay the same.

One small piece of infrastructure released a bottleneck across multiple streams.

Chaining the Security

Just because we're bridging two worlds doesn't mean we leave gaps in between. The security chain, from caller to CRM, looks like this:

  1. A cloud service authenticates with its managed identity and sends a request to the proxy.
  2. Entra ID validates the caller's identity at the platform level. Only services explicitly granted access are let through. Everything else is rejected.
  3. The proxy sits in a private VNet connected to the on-premises network. All traffic travels through a VPN encrypted tunnel — no public endpoints.
  4. The proxy authenticates to CRM using domain credentials stored securely in Azure Key Vault, naturally secured through Managed Identity for the proxy, over the private connection.
  5. CRM responds as it would to any authenticated internal client. The proxy returns the response as-is to the caller.

No anonymous hops. No open doors. Every leg — identity, network, credentials — is independently secured. CRM remains as closed as it always was.

In a transformation programme, bridges between old and new tend to become permanent fixtures long before anyone planned for them to be. They have to be secured properly from day one.

Pros and Cons

No approach is without trade-offs. Here's an honest look at this one.

Pros:

  • Unblocks multiple workstreams immediately without waiting for the full platform migration
  • Cloud services integrate using modern patterns — managed identities, OAuth — so nothing is throwaway when Dataverse arrives
  • CRM stays untouched. No AD FS retrofit, no configuration changes, no risk to a running production system
  • The security chain is unbroken across identity, network, and credentials
  • Small footprint — about 90 lines of code, easy to understand, easy to maintain, easy to hand over
  • Fully automated deployment managed via standard pipelines, including automated scaling and native integration with our existing observability stack

Cons:

  • The domain credentials in Key Vault need to be rotated and managed. It's worth having a dedicated CRM service account for the proxy so credential rotation is isolated and predictable
  • It doesn't support Dataverse connectors — but the HTTP connector works perfectly and is future-proof. When Dataverse arrives, you swap the endpoint and the integrations carry over as-is

The way I see it, the pros far outweigh the cons for this specific situation. The alternative was stalling half the programme, or having multiple Function Apps communicating through the VPN layer individually, losing all the advantages of low-code/no-code platforms like Logic Apps and Power Automate. But it's worth being honest about what you're taking on when you build a bridge like this.

If you see any other cons I might be missing, I'd love to hear them — drop a comment.

A Note on Efficiency

While NTLM is notoriously "chatty," we didn't need a complex scaling strategy to handle the overhead. By leveraging native library support for connection pooling and NTLM authentication caching, the proxy maintains established authenticated state. This reduces the 3-way handshake to a single-trip execution for pooled requests, keeping the 90-line footprint performant even under load.

What I Took Away From This

I've delivered enough programmes to know that the hardest problems aren't always the biggest ones. Sometimes it's a small, obscure gap between the old world and the new world that quietly holds everything up. The kind of thing that doesn't show up on a programme plan until three workstreams are all blocked on the same dependency.

Dynamics CRM On-Premises without AD FS, authenticating purely via NTLM within a closed network, is more common than people realise. It's not a failure of architecture — it's just a system that was built for a different era and a different set of requirements. The challenge is bridging the gap without derailing the programme to fix the legacy system first.

The entire proxy is about 90 lines of code. The deployment automation is longer than the application. The programme impact was disproportionately large for something so small.

Sometimes the best architecture decision isn't a managed service, a platform migration, or a six-month AD FS rollout. It's a small, focused piece of code that does exactly one thing well — and lets everything else move forward.

#DigitalTransformation #Azure #LegacyModernization #DynamicsCRM #EnterpriseIntegration

To view or add a comment, sign in

More articles by Aylin Ahmed

Others also viewed

Explore content categories