Passwordless identity for services and pipelines across Microsoft 365 and Azure

Mailbox assistants, legacy relays, web forms and deploy pipelines all needed access to something. We gave each one a certificate or a federated identity scoped to exactly one thing, and left nothing behind to leak.

Locality
UK
Disciplines
Identity and security, Cloud and infrastructure
Stack
  • Microsoft Entra ID
  • Microsoft Graph
  • Exchange RBAC
  • Azure Key Vault
  • Workload identity federation
  • Azure DevOps

At a glance

  • 7

    service identities on the pattern

  • 1

    mailbox reachable per credential

  • 4

    Exchange RBAC scopes

  • 0

    client secrets stored in pipelines

The challenge

Several services needed to read, draft or send mail: an AI mailbox assistant, applications that only speak SMTP, a website contact form and a campaign mailer. The old answer was a user password or SMTP basic authentication, which Microsoft is retiring, and tenant-wide mail permission means one leaked credential can read every mailbox in the business.

The deploy pipelines had the same problem in a different shape, with long-lived tokens and keys sitting in a code host. We wanted one repeatable pattern where every service and every pipeline proves who it is without a stored secret.

Architecture

Least-privilege service identity architectureArchitecture diagram with 10 components. Service container to Entra ID (signed assertion). Entra ID to Microsoft Graph (token). Microsoft Graph to Exchange RBAC. Exchange RBAC to Assigned mailbox. Exchange RBAC to Every other mailbox. Pipeline job to Federated credential. Federated credential to Key Vault. Key Vault to Deploy target.Service to mailboxPipeline to targetsigned assertiontokenService containerCertificate from vaultEntra IDClient credentialsMicrosoft GraphExchange RBACCustom resource scopeAssigned mailboxEvery other mailbox403Pipeline jobFederated credentialOIDC, no secretKey VaultOne secret per connectionDeploy targetHealth check + rollback

Swipe to explore the diagram

Services authenticate with a certificate and are scoped to one mailbox by Exchange. Pipelines authenticate with a federated token and can read only their own secrets.

What we built

The service pattern

  • One Entra app registration per service or purpose, using client credentials with a certificate: no user, no password and no client secret
  • Graph application permissions narrowed to named mailboxes with Exchange RBAC for Applications
  • A scripted setup in az CLI and Exchange Online PowerShell, followed by a check that proves each app works on its own mailbox and fails on every other one

The pipeline pattern

  • Azure DevOps service connections using workload identity federation, each scoped to one resource group or a handful of vault secrets
  • Key Vault in RBAC mode with purge protection, and per-secret role assignments so a deploy reads only what it needs
  • Static-site deploy tokens fetched at deploy time rather than stored anywhere

Legacy SMTP

  • A small SMTP-to-Graph relay for applications that only speak SMTP, locked to a single sender per instance and never published on the host
  • Messages over 4 MB routed through Graph upload sessions instead of bouncing
  • Strict DMARC alignment on the sending domain, with SPF and DKIM verified at an external receiver

Engineering decisions

Certificates, not secrets

The private half of a certificate never leaves the vault and the container. There's nothing to paste into a log, and the only recurring job is a roll-over every two years.

Make RBAC the only authority

Entra grants and Exchange RBAC are additive, so one unscoped grant quietly defeats the mailbox scoping. The runbook removes the Entra grant and confirms every app's assignment list comes back empty.

Separate credentials for separate jobs

A draft-only assistant and a service that sends mail never share a credential, so each can be rotated or revoked on its own.

Scope to a resource group, not a subscription

Each pipeline identity can touch one resource group or one set of secrets. A compromised pipeline in one project can't reach into another.

Outcome

Every mail workload runs without a user password, with a blast radius of one mailbox. Pipelines deploy with no stored cloud credentials, and the tokens that used to live in the old code host have been reset and retired.