M365 Show -  Microsoft 365 Digital Workplace Daily
M365 Show with Mirko Peters - Microsoft 365 Digital Workplace Daily
Ditch Passwords—How Real Azure Apps Secure Everything
0:00
-20:16

Ditch Passwords—How Real Azure Apps Secure Everything

Here’s a fun fact: embedding credentials in your Azure apps is basically handing out house keys at a bus stop. Entra ID and managed identities let you lock every door without juggling keyrings or hoping nobody notices the Post-It note under your keyboard. The good news—you don’t need to be a cryptography wizard to do this. I’ll show you step by step how to swap secrets for tokens and sleep better at night.

The Doormat Key Problem

Why do so many Azure apps still stash passwords in config files like we’re all still writing VBScript in 2003? Seriously, it’s 2024. We have cloud-native security systems that mint tokens on demand, yet someone somewhere is still committing a literal `sa` password to their repo like it’s a badge of honor. And the excuse is always the same: “We hard‑code it just to save time.” Save time today, and then spend weeks cleaning up the mess when it leaks. That's not a shortcut. That’s procrastination with extra steps.

The problem is bigger than laziness. Developers think dropping usernames and passwords into a web.config file or appsettings.json is harmless because it stays internal. Except nothing ever stays internal. That config gets copied to dev, test, staging, three different QA branches, backups, and a laptop someone left on a plane. That’s not a secret; that’s a distributed broadcast. Add in Git, where “oops, wrong push” has put more production passwords public than I care to count, and you’ve got an incident queue that writes itself.

Here’s the part nobody likes to admit: these “quick fixes” don’t just risk exposure—they guarantee it over time. Secrets are slippery. They creep into log files because you forgot to sanitize an exception. They hide in screenshots shared over Teams. They get zipped into backups sitting unencrypted in blob storage because no one paid for the vault tier. All it takes is one bored attacker scanning public repos for obvious strings—`Password123!` is still a goldmine—and suddenly your entire app is wide open.

One of my favorites? Remember when thousands of credentials showed up in public GitHub a few years back because devs used personal repos for “just testing”? Attackers didn’t even have to try. They ran keyword scans, found connection strings, and walked straight into production resources. No zero‑day. No Hollywood hacking montage. Just copy, paste, profit. That’s what hard‑coding secrets buys you—a house where the burglar doesn’t even need to pick a lock. The key’s under the mat, and you spray‑painted “KEY IS UNDER REACT APP SETTINGS” on the front porch.

You wouldn’t leave your front door unlocked with the garage code written on a sticky note, but that’s exactly how connection strings behave when they include credentials. Sure, it works. Until a neighbor—by which I mean some anonymous botnet—figures out where you hid them. Microsoft has been very clear these days: hard‑coded credentials are being pushed into the same bucket as Internet Explorer and Clippy. Deprecated. You can limp along with them, but expect disappointment, breakage, and an audit log screaming at you.

Add to that the sprawl problem. Each environment needs its own settings, so now you’ve got a password per dev box, an admin string in staging, another one production, and nobody knows if they’re rotated. Different teams hoard slightly out‑of‑date copies. Someone comments out an old connection string instead of deleting it. Congratulations: your app is a digital junk drawer of skeleton keys. Attackers love it because it’s a buffet. And let’s not even mention what happens when contractors get read‑access to your repos. You think they only take the code?

The takeaway here is simple: the real danger isn’t just a password leaking. It’s the way secrets breed. Once you let them into configs, they replicate across environments, backups, scripts, and documentation. You cannot manage that sprawl. You cannot contain it with “clever” obfuscation tricks. It’s not a problem you patch; it’s a problem you eliminate. Stop thinking about where to hide the key. Instead, stop using keys at all.

That’s why tokens exist. They don’t behave like passwords. They aren’t long‑lived, they aren’t static, and they don’t sit in files for years daring the wrong person to find them. The cure for password sprawl isn’t to hide the passwords better—it’s to replace them with something that self‑destructs when it’s misused. Tokens let you do exactly that, and Entra ID is the system handing them out.

Okay, so if we throw the doormat keys away, how do tokens avoid turning into even messier problems for us admins? Let’s talk about why they actually make life easier instead of harder.

Why Tokens Beat Passwords Every Time

If passwords are car keys, tokens are valet tickets—you use them for a single ride, and they’re worthless once the trip’s done. Nobody makes a sneaky copy of a valet ticket, and if they try, someone spots it right away. That’s the fundamental difference: passwords are static. Tokens are temporary and scoped. Which means if they leak, the blast radius is tiny, and the clock is already ticking before they expire.

So what even is a token? In Azure land, your app hits Entra ID and says, “Hey, can I get permission to do this thing?” Entra ID checks who or what’s asking, does its paperwork, and then hands back a signed package: the access token. That token is short‑lived. It’s tied to the user or service identity. It’s got limits on what it can touch. And when the time window closes, it’s dead. Nobody resets it, nobody rotates it—it just vanishes.

Of course, when you tell devs this, the first instinct is panic. “Oh no, now we’ve got rotating tokens flying around, do we have to cache them, store them, chase refresh codes, build replacement logic?” The answer: no, stop sweating. The Microsoft Identity libraries do that plumbing for you. The SDKs literally grab, refresh, and dispose like janitors cleaning up after a conference. You don’t have to reinvent OAuth. You just call the function and focus on your actual app logic.

Compare it with the old school way. A static password is like handing someone the master key to your entire building. They don’t just get into their floor; they can hit the executive suite, the server room, even the candy stash in HR’s drawer. Now take a token: it’s a guest pass that only works for Floor 3, and it shuts off at 5 p.m. If someone tries to use it after hours, access denied. No guessing, no lingering. It’s scope matched to function, not wide open duct tape.

Tokens also carry brains inside them. Ever open up a token? It’s like inspecting a boarding pass—there’s your flight, seat row, gate, and zone. Tokens store claims: roles, scopes, tenant IDs, even the user’s basic info. That means your API isn’t just trusting “this person logged in.” It’s checking “this person is in finance, has the payments role, and was approved today.” You can build way tighter rules directly into your app logic, without managing ten different password sets.

Here’s a real comparison. One shop I worked with had a database locked only with SQL credentials hard‑coded everywhere. That account had system admin. Predictably, one contractor copied it to a tool, and bam—the entire database was their playground. Every table exposed. Now look at a newer system using tokens scoped only to what the app needed: read access on a single schema. Even if someone stole that token, all they could do was select records, and only until the token expired. It turned a nightmare into a minor annoyance.

Now I can hear some devs groaning: “Tokens sound neat in theory, but they’ll break my workflow. I don’t have time to micromanage renewal or expiration.” That’s the myth. Nobody’s expecting you to refresh them manually. The libraries you’re already using in .NET or Node grab the refresh token, swap it invisibly, and keep rolling. You don’t even know it happened unless you were sniffing packets. Which is the point—stop babysitting secrets; let the system handle it.

Think of Entra ID as the passport office. You show up, prove who you are, and they stamp an ID that border guards (your APIs) trust. Expired stamp? No boarding. Wrong stamp? Denied. It centralizes the identity question in one authority. Your apps don’t argue, your APIs don’t babysit passwords—they just check the stamp and let you through. That’s infinitely better than juggling a dozen mystery keys and wondering which ones are still valid.

The best part for us IT folks: no more forced password rotations every 90 days, no more scripts running at 3 a.m. to reset service accounts, no more debates about whether to store secrets in Key Vault or under a heavy dose of wishful thinking. Tokens expire on their own, and that expiration is your safety net. If something leaks, it’s already doomed to stop working. You didn’t fix anything—you just built a system that auto‑heals by design.

So yes, tokens handle user logins securely and cleanly. Great. But it’s not just about users clicking sign‑in. The real test is when apps need to talk to other apps, or services fire off requests at three in the morning without a human anywhere near a keyboard. That’s where things shift from neat to necessary.

Meet Managed Identities: Service Principals, but Less Dumb

Imagine if your app could march into Azure services without ever typing a password, because it already came out of the factory with its own stamped identity. That’s not science fiction—it’s how Managed Identities work. And once you start using them, the whole idea of shuffling service principals around with expiring secrets will feel as dated as carrying a pager.

Here’s the old mess we used to live with: service principals. You’d register one, generate a client secret or maybe a certificate, and stuff that into Key Vault. Then you hand that identity to your app so it could do things like connect to SQL or hit Storage. On paper, it sounded fine—credentials weren’t hard‑coded anymore, they were centralized. In reality, you were babysitting hot potatoes that expired every one to two years. You’d set an Outlook reminder called “don’t let prod die,” then still forget until the Sunday outage alert hit. And if you ever logged secrets to the wrong console, congratulations—you spray painted your vault on a billboard.

Sure, vaults were better than config strings. But you were still juggling. You had to rotate secrets by hand or automate it with scripts. If the rotation failed, your app died in silence. And let’s not pretend most shops ever tested rotations in staging—they just waited for the day prod refused to start. How many of us have been woken up by that “can’t connect to database” ticket, only to discover the mystery string expired at midnight? Too many.

That’s where Managed Identities flip the table. You spin up a resource—say, an Azure App Service, a Function, or a VM—and you flip a switch: “Assign Managed Identity: On.” Done. Behind the curtain, Azure creates an identity object in Entra ID that your resource can use. No secret strings. No vault renewal dates. It’s the app version of being born with a driver’s license. You don’t carry a laminated key card. The cloud already knows who you are, and it vouches for you any time you need to talk to another resource.

Think about it like this: every app used to be a teenager borrowing dad’s car. They had to carry around the keys, and if they lost them, game over. With Managed Identity, the app walked into the DMV the second it was created, got its own license and insurance, and now it doesn’t need to ask permission at midnight. No spare keys taped under the bumper.

Technically, here’s what’s happening. Your app calls the Instance Metadata Service, that weird link that only exists inside Azure VMs and services. It says, “Hey, I need a token to talk to SQL.” The metadata service goes, “Okay, I know your identity because you’re running inside an approved resource. Here’s a signed, short‑lived token straight from Entra ID.” Boom—no handling credentials, no rotating, no vault. You just get a JWT you can send to SQL or Storage, and the target validates it against Entra ID.

Example time. You’ve got a web app that needs to query a SQL Database. Old setup: you hide a SQL username and password in Key Vault, and the app pulls it at runtime. You spend half your life worrying if it was rotated correctly, if the Key Vault references work in staging, and if the devs pushed the right policy. New setup: turn on Managed Identity for the web app, add it as a database user in SQL with the needed role, and you’re done. The app asks Azure for a token, uses it in the connection string, and never once sees a password. There’s nothing to rotate, and nothing to leak in logs.

The benefits pile up fast. Secrets completely vanish from repos and configs, so the odds of devs accidentally uploading sensitive strings go from near‑certainty to practically zero. Rotation becomes automatic—tokens age out on their own, there’s no midnight panic renewals. And because identities are Entra ID objects, you grant them least privilege. Your web app doesn’t need contributor to your entire subscription; it only needs read access to one storage container. You make that assignment once, and Azure enforces it.

Bottom line—Managed Identities turn service communication from “hope you didn’t leak the keys” into “it just works.” They give you secure defaults without the cleanup duty. It’s the closest thing to magic we get in Azure security.

Of course, an ID is only useful if the directory actually knows about it and what it can do. And that’s where app registrations come into play—because paperwork still matters, even for cloud identities.

App Registrations Without Losing Your Sanity

App registrations are the DMV of Entra ID—nobody wakes up excited to deal with them, but without the paperwork, you’re not driving anywhere. They’re boring, they’re picky, and you absolutely have to do them. This is the blueprint step: it’s what tells Entra ID how your application identifies itself, which APIs it can talk to, and how it will ask for permission. If you skip it or slap it together wrong, you’ll build an application that might technically run but ignores every security rule along the way. And much like skipping the DMV, eventually somebody pulls you over.

At its core, an app registration is just an identity card for an application. It defines how the app can sign in—whether it’s users logging in with accounts or the app itself acting as a service identity. It also holds the permissions and endpoints involved. That means scopes, roles, redirect URIs, and just enough red tape to make you roll your eyes. But this red tape is the reason the whole thing works. Without it, Azure doesn’t know who to trust and APIs don’t know when to slam the door.

Here’s the fun part: it’s not complicated until you get it wrong. Mess up a redirect URI? Suddenly your app is open like a carnival ride with no seatbelts. Mix up scopes with roles? You’ve just granted privileges about twelve levels higher than you intended. That’s how a web portal meant to read profile info ends up writing to calendars, wiping contacts, and scaring compliance officers. And Microsoft Graph is the classic landmine here. How many apps have been spotted with full Graph read‑write secured by nothing more than one lazy registration? Way too many. They didn’t need everything—they needed `profile.read`. But because someone didn’t stop to check, they granted the developer version of god mode.

So let’s cut through the jargon. Scopes are what an app can do **on behalf of a user**. Roles are what the app itself is allowed to perform. Think of it like a school. Scopes are the hall passes: “Emily can leave class to go to the library.” Roles are the job titles: “Emily is a librarian, so she’s allowed behind the circulation desk.” If you mix those up, you’ve given every student a staff badge. Nobody wants a hallway full of teenagers with server room access, but that’s what lazy app registrations create.

And then there’s the consent flow. Users and admins get asked to approve what the application is requesting at sign‑in time. This is where most people’s eyes glaze over. But here’s the truth: those consent prompts are your audit trail. They tell you what the app is asking for and who approved it. If you skip them or train users to blindly hit “Accept,” you’re basically running an honor system at the vault door. Which means you don’t have a vault anymore—you have an invitation to chaos.

There’s a simple move that saves headaches: combining Easy Auth with real claims checks inside your APIs. Easy Auth lets you wire up authentication with almost no code—flip the switch, and your front door works. But don’t stop there. Build claims checks so your APIs look at the token, inspect the scopes or roles embedded, and then enforce rules. That way even if someone forwards a token around, the API itself makes sure only the right halls are open.

One shop I worked with used Easy Auth on an internal portal but skipped the claims checks. It worked fine until someone copied a user’s token and called the APIs directly. Nothing in their setup validated whether the caller was supposed to hit those endpoints. By the time they caught it, half their tenant data had been poked at. Dropping a simple role check would have shut that down instantly. Security isn’t always about more tools—it’s about using the ones you already have properly.

When you configure app registrations the right way, something magical happens: random API clients stop sneaking through. Because the rules are tight, the tokens don’t lie, and consent is properly logged. Your apps only talk to what they’re allowed to. Your users only grant what’s absolutely needed. And your repo stops being a dumping ground for accidental privileges.

So now you’ve got users logging in correctly, services with real IDs, and APIs that aren’t left wide open. The next question is what happens when an app doesn’t just act for itself—it needs to impersonate a user or pass their access down the chain. That’s where the real fun begins.

Subscribe to the newsletter at m365 dot show or follow us on the M365.Show page for livestreams with MVPs who’ve been through this exact grind and learned the hard lessons, so you don’t have to.

Forwarding Tokens Without Breaking the Chain

Ever tried passing a note in class, only for it to get rewritten three or four times before it finally reaches the teacher? Half the message is lost, someone doodles in the margins, and by the end it barely makes sense. That’s exactly what forwarding user tokens feels like when you set it up wrong. You think the user’s identity is making its way down the line, but by the time it hits the last service it’s unrecognizable, or worse, rejected completely.

Here’s the scenario we’re really dealing with: you’ve got a user logging in through a front‑end app. That front‑end app doesn’t do all the heavy lifting itself—it calls another back‑end API to do the actual work. Sounds fine, but the catch is you still need that original user’s identity to travel with the request. If it doesn’t, the back‑end API is just guessing about who the request belongs to. So delegation has to be chained. Front‑end proves who the user is, passes that context down, and the back‑end keeps that thread intact. Simple in theory. In reality? It’s a minefield.

Because here’s where things blow up. If you just forward the original access token directly, you run into the “double hop” problem. The token was issued for App A to call Resource X, but then App A tries to use the same token to call App B. App B sees the mismatch and says, “Nope, token not valid for me.” And suddenly your neat user experience unravels into extra prompts, broken claims, or worse, another consent popup for the same user who already signed in. Nothing kills trust faster than a line of users thinking, “Didn’t I already allow this yesterday?”

The right way to fix this isn’t duct tape. You need to use OAuth 2.0’s On‑Behalf‑Of flow, or OBO if you enjoy shorter acronyms. The idea is straightforward: each service doesn’t just forward the original token. It takes that incoming token, proves it to Entra ID, and then trades it in for a new, fresh token that’s valid for the next API in line. It’s like the user hands the first service a hall pass, and instead of trying to sneak it through every door, that service swaps it out at the office for a proper pass to the next hallway. Each hop gets a token scoped to its role, and security stays intact.

Now, if you’ve built this by hand, you know it can feel like juggling knives. Parsing tokens, handling refreshes, swapping them at the right point. Luckily, middleware like Easy Auth makes this tolerable. Easy Auth does the dull bits—storing the token, refreshing it, and offering a helper call to grab it on demand. Instead of rolling weird hacks that shove tokens through headers and praying nothing leaks into logs, you let middleware take the night shift. You avoid your dev team “helpfully” building a custom solution that logs the token plain‑text for debugging, and then forgets to strip it.

That’s a risk you can’t ignore. Passing raw tokens blindly is like copying your driver’s license and handing it around at every door. Too much personal info ends up where it doesn’t belong, and suddenly anyone who intercepts the request has way more than they should. Not only does that risk leaks of actual identity data, it also flattens security boundaries. APIs stop being able to draw clear lines between who a user is and what the service should be trusted with. The result? You either over‑grant permissions or block legitimate calls. Both are nightmares: one leaks data, the other drowns your helpdesk in complaints.

Let’s run through a clean example. A user signs into a front‑end web app. That app calls a protected API, but instead of forwarding the raw access token, it uses OBO to get a new token valid just for that API. Now the API wants some data from SQL. Perfect—that’s not a user’s direct concern, so the API doesn’t forward anything. It grabs a system token through a Managed Identity, connects to SQL, runs its queries, and sends just the needed results back. At every stage the identity context is clear: user for the app and API, system identity for the database. Each token is scoped, short‑lived, and verifiable.

The payoff here is huge: services chain together without confusing the identity signal. Users don’t get spammed with extra consent prompts, your APIs enforce the right claims without confusion, and you avoid putting raw keys on the wire where they don’t belong. OBO is less about extra work and more about keeping the chain of trust unbroken from front‑end to back‑end, all the way down to the database.

By now, you’re probably noticing a pattern: users, services, APIs, all tightened up under Entra ID. Each part is secured the right way, without the mess of passwords or sprawling secrets. So the real question is—what’s the practical gain for us after doing all this heavy lifting?

Subscribe to the newsletter at m365 dot show or follow the M365.Show page for livestreams with MVPs who know these scars firsthand and can walk you through the safer patterns before you live through the outages.

Conclusion

Hard‑coded secrets aren’t a clever shortcut; they’re legacy baggage. They spread across configs, repos, and backups until you’re basically running a scavenger hunt for hackers. Entra ID and managed identities slice that risk out completely—your apps get trusted, short‑lived tokens without you juggling passwords like it’s 1999. Even Microsoft flatly says secrets in the cloud are deprecated, so stop propping them up. While you’re at it, remember MFA blocks over 99.2% of compromise attempts, so layer that on top.

Subscribe at m365 dot show—because the next rename will land before your Azure Portal learns how to load faster.

Discussion about this episode

User's avatar