<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Manoj Dwivedi</title>
    <description>The latest articles on DEV Community by Manoj Dwivedi (@manoj_dwivedi_f9b67f38422).</description>
    <link>https://dev.to/manoj_dwivedi_f9b67f38422</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3687552%2Ffae67ffa-06c7-4538-a728-1fcc861949e0.jpg</url>
      <title>DEV Community: Manoj Dwivedi</title>
      <link>https://dev.to/manoj_dwivedi_f9b67f38422</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/manoj_dwivedi_f9b67f38422"/>
    <language>en</language>
    <item>
      <title>Delegated vs App-Only Auth in Microsoft 365 Migrations (Hard Lessons)</title>
      <dc:creator>Manoj Dwivedi</dc:creator>
      <pubDate>Thu, 08 Jan 2026 11:34:50 +0000</pubDate>
      <link>https://dev.to/manoj_dwivedi_f9b67f38422/delegated-vs-app-only-auth-in-microsoft-365-migrations-hard-lessons-125i</link>
      <guid>https://dev.to/manoj_dwivedi_f9b67f38422/delegated-vs-app-only-auth-in-microsoft-365-migrations-hard-lessons-125i</guid>
      <description>&lt;p&gt;If you’ve ever built a Microsoft 365 migration script, chances are you started with delegated authentication.&lt;/p&gt;

&lt;p&gt;I did too.&lt;/p&gt;

&lt;p&gt;It’s quick to set up, easy to debug, and works fine for a handful of users. The problem is that delegated auth has a habit of failing only after you trust it — usually halfway through a long-running migration.&lt;/p&gt;

&lt;p&gt;This post isn’t about what Microsoft recommends. It’s about what breaks in real tenant-to-tenant migrations and why app-only auth eventually becomes unavoidable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Delegated Auth Feels Like the Right Choice (At First)
&lt;/h2&gt;

&lt;p&gt;Delegated auth is appealing because it feels familiar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in as admin&lt;/li&gt;
&lt;li&gt;Consent permissions&lt;/li&gt;
&lt;li&gt;Run the script&lt;/li&gt;
&lt;li&gt;Watch data move&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For POCs and demos, it works surprisingly well. That’s why so many migration scripts online are built this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trouble starts when:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Jobs run for hours&lt;/li&gt;
&lt;li&gt;You add concurrency&lt;/li&gt;
&lt;li&gt;You migrate more than a few dozen users&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s when delegated auth shows its real limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Failure Is Usually Silent
&lt;/h2&gt;

&lt;p&gt;The first time delegated auth fails, it often doesn’t throw a clean error.&lt;/p&gt;

&lt;p&gt;Instead, you’ll see things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests suddenly returning 401&lt;/li&gt;
&lt;li&gt;Token refresh loops&lt;/li&gt;
&lt;li&gt;Background jobs stopping without obvious logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What actually happened?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The session expired&lt;/li&gt;
&lt;li&gt;MFA kicked in&lt;/li&gt;
&lt;li&gt;The refresh token became invalid&lt;/li&gt;
&lt;li&gt;The user context was no longer trusted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are easy to diagnose mid-migration.&lt;/p&gt;

&lt;p&gt;User-Based Throttling Is a Hidden Problem&lt;/p&gt;

&lt;p&gt;Delegated auth ties every request to a user identity.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;p&gt;You hit user-based throttling faster&lt;/p&gt;

&lt;p&gt;Parallelism collapses under load&lt;/p&gt;

&lt;p&gt;Retry storms become more likely&lt;/p&gt;

&lt;p&gt;In one migration, we increased concurrency to speed things up and actually made it slower. Requests started backing off constantly because everything was bound to a single admin user.&lt;/p&gt;

&lt;p&gt;At that point, no amount of retry logic helps.&lt;/p&gt;

&lt;p&gt;Background Jobs and Delegated Auth Don’t Mix&lt;/p&gt;

&lt;p&gt;Tenant migrations are not interactive tasks.&lt;br&gt;
They:&lt;/p&gt;

&lt;p&gt;Run overnight&lt;/p&gt;

&lt;p&gt;Span days&lt;/p&gt;

&lt;p&gt;Resume after failures&lt;/p&gt;

&lt;p&gt;Require retries long after the original process started&lt;/p&gt;

&lt;p&gt;Delegated auth assumes there’s a user session somewhere behind the scenes.&lt;/p&gt;

&lt;p&gt;App-only auth doesn’t.&lt;/p&gt;

&lt;p&gt;This difference alone is enough to rule out delegated auth for serious migrations.&lt;/p&gt;

&lt;p&gt;App-Only Auth Is Boring — and That’s a Good Thing&lt;/p&gt;

&lt;p&gt;Once you switch to app-only authentication, something interesting happens: things get boring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No pop-ups.&lt;/li&gt;
&lt;li&gt;No session expiry.&lt;/li&gt;
&lt;li&gt;No MFA surprises.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable tokens&lt;/li&gt;
&lt;li&gt;Predictable throttling behavior&lt;/li&gt;
&lt;li&gt;Clean separation between identity and execution
It’s not flashy, but it’s reliable — which is exactly what migrations need.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Permissions Are Scarier, but More Honest&lt;/p&gt;

&lt;p&gt;One valid concern with app-only auth is permission scope. Granting tenant-wide permissions feels dangerous, and it should.&lt;/p&gt;

&lt;p&gt;But here’s the uncomfortable truth:&lt;br&gt;
If you’re migrating tenant data at scale, you already need broad access.&lt;/p&gt;

&lt;p&gt;Delegated auth doesn’t make that safer — it just hides it behind a user account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With app-only auth:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permissions are explicit&lt;/li&gt;
&lt;li&gt;Access is auditable&lt;/li&gt;
&lt;li&gt;Rotation is easier&lt;/li&gt;
&lt;li&gt;Responsibility is clearer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s a better security posture, not a worse one.&lt;/p&gt;

&lt;p&gt;Retry Logic Behaves Differently (and Better)&lt;/p&gt;

&lt;p&gt;This was an unexpected benefit.&lt;/p&gt;

&lt;p&gt;With delegated auth, retries often fail for reasons unrelated to load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token refresh failures&lt;/li&gt;
&lt;li&gt;Session invalidation&lt;/li&gt;
&lt;li&gt;Conditional access policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With app-only auth, retries usually fail for real reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Throttling&lt;/li&gt;
&lt;li&gt;Temporary service issues&lt;/li&gt;
&lt;li&gt;That makes retry logic simpler and more predictable.&lt;/li&gt;
&lt;li&gt;When Delegated Auth Is Still Okay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Delegated auth isn’t useless. It’s fine for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-off admin tasks&lt;/li&gt;
&lt;li&gt;Small migrations&lt;/li&gt;
&lt;li&gt;Validation scripts&lt;/li&gt;
&lt;li&gt;Exploratory tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mistake is assuming it will scale just because it worked once.&lt;/p&gt;

&lt;p&gt;The Rule I Follow Now&lt;/p&gt;

&lt;p&gt;My rule of thumb is simple:&lt;/p&gt;

&lt;p&gt;If the process needs to survive a restart, it should not depend on a user session.&lt;/p&gt;

&lt;p&gt;Tenant-to-tenant migration definitely falls into that category.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most migration failures don’t come from bad code.&lt;br&gt;
They come from wrong assumptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delegated auth assumes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short execution&lt;/li&gt;
&lt;li&gt;Stable sessions&lt;/li&gt;
&lt;li&gt;Low concurrency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tenant migrations assume none of those things.&lt;/p&gt;

&lt;p&gt;If you’re serious about migrating Microsoft 365 tenants at scale, app-only authentication isn’t an optimization — it’s a prerequisite that is also used in &lt;a href="https://cloudbik.com" rel="noopener noreferrer"&gt;CloudBik&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>microsoft</category>
      <category>api</category>
    </item>
    <item>
      <title>Microsoft 365 Tenant-to-Tenant Migration: What Actually Breaks at Scale (and How to Design for It)</title>
      <dc:creator>Manoj Dwivedi</dc:creator>
      <pubDate>Wed, 31 Dec 2025 13:21:05 +0000</pubDate>
      <link>https://dev.to/manoj_dwivedi_f9b67f38422/microsoft-365-tenant-to-tenant-migration-what-actually-breaks-at-scale-and-how-to-design-for-it-4ohl</link>
      <guid>https://dev.to/manoj_dwivedi_f9b67f38422/microsoft-365-tenant-to-tenant-migration-what-actually-breaks-at-scale-and-how-to-design-for-it-4ohl</guid>
      <description>&lt;p&gt;When people talk about Microsoft 365 tenant-to-tenant migration, it often sounds straightforward: authenticate to both tenants, copy data, validate, done.&lt;/p&gt;

&lt;p&gt;In reality, once you move beyond a few test users, things break in ways that aren’t obvious from documentation.&lt;/p&gt;

&lt;p&gt;After working on multiple tenant migrations involving Exchange Online, OneDrive, SharePoint, and Teams, I’ve learned that the real challenge isn’t copying data — it’s designing a system that survives scale, throttling, and long-running execution.&lt;/p&gt;

&lt;p&gt;This post isn’t a product pitch or a checklist. It’s a practical look at what actually causes pain in tenant migrations and how to design around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tenant Migration Is Not a One-Time Copy Job
&lt;/h2&gt;

&lt;p&gt;The biggest mistake teams make is treating tenant migration like a batch job.&lt;/p&gt;

&lt;p&gt;It’s not.&lt;/p&gt;

&lt;p&gt;Tenant-to-tenant migration behaves more like cloud data replication:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have two isolated systems&lt;/li&gt;
&lt;li&gt;Changes continue to happen during migration&lt;/li&gt;
&lt;li&gt;APIs impose strict limits&lt;/li&gt;
&lt;li&gt;Failures are guaranteed over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you accept that model, many design decisions become clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegated Auth Fails Sooner Than You Think
&lt;/h2&gt;

&lt;p&gt;Delegated authentication feels convenient at first, especially for proof-of-concept migrations. But it breaks down quickly in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common issues:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Token expiration mid-job&lt;/li&gt;
&lt;li&gt;User-based throttling&lt;/li&gt;
&lt;li&gt;Background jobs failing when sessions expire&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For anything beyond a pilot, application-based authentication is mandatory. It gives you predictable behavior, stable permissions, and far fewer surprises during long-running operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Throttling Is Not an Error Condition
&lt;/h2&gt;

&lt;p&gt;Microsoft Graph throttling (429, 503) is not a bug. It’s the platform telling you to slow down.&lt;/p&gt;

&lt;p&gt;What causes trouble is when migration logic treats throttling as a failure instead of a signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At scale, these patterns work far better:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Respect Retry-After headers&lt;/li&gt;
&lt;li&gt;Use exponential backoff&lt;/li&gt;
&lt;li&gt;Limit concurrency per workload&lt;/li&gt;
&lt;li&gt;Reduce batch sizes dynamically during peak hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unbounded parallelism might look fast initially, but it almost always collapses under sustained load.&lt;/p&gt;

&lt;p&gt;Batching Helps — Until It Doesn’t&lt;/p&gt;

&lt;p&gt;Graph batching (up to 20 requests per batch) is useful, but it’s not a silver bullet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batching works best when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests are uniform&lt;/li&gt;
&lt;li&gt;Payload sizes are small&lt;/li&gt;
&lt;li&gt;Retry logic is granular&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problems appear when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single failed request blocks the whole batch&lt;/li&gt;
&lt;li&gt;Payloads become too large&lt;/li&gt;
&lt;li&gt;You retry entire batches instead of individual operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, the best systems mix small batches with fine-grained retry handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration Order Matters More Than Tools
&lt;/h2&gt;

&lt;p&gt;Another common issue is migrating workloads in the wrong order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A stable sequence looks like this:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exchange Online&lt;/li&gt;
&lt;li&gt;OneDrive&lt;/li&gt;
&lt;li&gt;SharePoint&lt;/li&gt;
&lt;li&gt;Microsoft Teams&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Teams depends heavily on SharePoint and Microsoft 365 Groups. Migrating it early almost guarantees partial results and cleanup work later.&lt;/p&gt;

&lt;p&gt;No tool can fix bad ordering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delta Sync Is What Saves Your Cutover
&lt;/h2&gt;

&lt;p&gt;Full re-syncs don’t scale well.&lt;/p&gt;

&lt;p&gt;Delta synchronization — based on timestamps or change tokens — is what makes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short cutovers possible&lt;/li&gt;
&lt;li&gt;Rollbacks manageable&lt;/li&gt;
&lt;li&gt;Re-runs safe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without delta sync, every re-run feels risky. With it, migrations become iterative and predictable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability Is Not Optional&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can’t answer these questions, you’re flying blind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which users are lagging?&lt;/li&gt;
&lt;li&gt;Which workload is throttled?&lt;/li&gt;
&lt;li&gt;How many retries are happening per hour?&lt;/li&gt;
&lt;li&gt;Where did failures start?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good migration systems log:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request IDs&lt;/li&gt;
&lt;li&gt;Batch IDs&lt;/li&gt;
&lt;li&gt;Retry counts&lt;/li&gt;
&lt;li&gt;Last successful timestamps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t about dashboards — it’s about knowing where to slow down or pause before things spiral.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d Tell Anyone Planning a Tenant Migration
&lt;/h2&gt;

&lt;p&gt;If you’re planning a Microsoft 365 tenant-to-tenant migration, keep this in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design for failure, not success&lt;/li&gt;
&lt;li&gt;Assume throttling will happen&lt;/li&gt;
&lt;li&gt;Treat migration as replication, not copy&lt;/li&gt;
&lt;li&gt;Prioritize observability early&lt;/li&gt;
&lt;li&gt;Respect workload dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you do that, migrations stop feeling chaotic and start feeling controlled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.cloudbik.com/solutions/microsoft-365-tenant-migrations/" rel="noopener noreferrer"&gt;Microsoft Tenant-to-tenant migration&lt;/a&gt; is one of those problems that looks easy until it isn’t.&lt;/p&gt;

&lt;p&gt;The teams that succeed aren’t the ones with the fastest scripts — they’re the ones that design with constraints in mind and accept that scale changes everything.&lt;/p&gt;

&lt;p&gt;If you approach migration as a distributed systems problem, Microsoft Graph becomes far more predictable — and migrations become far less stressful.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>microsoftgraph</category>
      <category>microsoft365</category>
      <category>office365</category>
    </item>
  </channel>
</rss>
