Tuesday, September 8, 2026

Moving Exchange Attribute Source of Authority (SOA) from On-Premises AD to Entra ID: A Hands-On Walkthrough

Standard

 

The Problem Most Hybrid Tenants Still Have

If you run a hybrid Exchange setup, you probably know this pain. Mailboxes live in Exchange Online, but every attribute change still has to go through on premises Active Directory first. That server you keep meaning to retire (the Last Exchange Server) stays around just so you can edit a custom attribute or update a proxy address.

Microsoft now lets you skip that. You can transfer the Source of Authority for Exchange attributes to the cloud, while identity attributes like name and UPN stay owned by on prem AD. Once a mailbox is cloud managed, you edit its Exchange attributes directly in Exchange Online, and if you set it up, those changes write back down to AD automatically.

I tested this recently, and a few things weren't obvious from the docs alone. Here's what I learned.

Stay tuned for my next blog post, where I’ll also cover group SOA and User SOA changes to Entra ID.

There are a few writeback options as well. If your servers are still tied to on premises AD, you might wonder why writeback even matters. Most companies now rely heavily on Entra ID, yet many of their servers are still attached to on prem AD and need to authenticate those same users and groups. By changing the SOA to Entra ID, you can still keep the relevant data up to date on premises as well. That makes Entra ID much easier to manage, and it's also a safer approach from a security standpoint.

With the current sync model, it only flows one way (even password writeback follows this path): AD to Entra ID. What we're doing here is the reverse, syncing back from Entra ID to AD. In this article Sync Exchange Attribute EXO to Onprem AD. 



 The Two Moving Parts

There's a mailbox property called IsExchangeCloudManaged. This is the real switch. Set it to true, and Exchange Online stops accepting attribute updates from on prem for that mailbox.

There are also two scoping attributes, CloudMastered and BlockExchangeAttributesOnPremisesSync. You don't set these directly. Cloud Sync calculates them from IsExchangeCloudManaged and the object's sync state. The default scoping rule needs both CloudMastered false and BlockExchangeAttributesOnPremisesSync true. Get the mailbox property right and these fall into place on their own.

What You Need First

  • Entra Connect Sync 2.5.190.0 or newer, if you're still running classic Connect alongside Cloud Sync
  • Cloud Sync provisioning agent 1.1.1107.0 or newer, showing Active under Agents
  • Hybrid Identity Administrator, or Global Administrator rights
  • No on prem Exchange server required for the PowerShell steps. Everything here runs through Exchange Online PowerShell. Useful if, like me, your access is AD only with no Exchange Management Shell.
My environment uses Entra Cloud Sync. The steps outlined below apply specifically to Cloud Sync provisioning.

Setting Up Writeback

In the Entra admin center, go to Entra Connect > Cloud Sync > New configuration > EXO to AD attribute sync. Confirm the agent matches your domain and create it, then hit Start provisioning.


Two tabs matter most:

Attribute mapping controls which Exchange properties flow back to AD.

Scoping filters shows the default rule plus anything custom you add. Custom clauses use OR against the default, so they widen scope, they don't replace it.

Transferring a Test Mailbox

Connect to Exchange Online PowerShell:

Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName admin@yourdomain.com

One thing to watch for. If your browser has a cached sign in for a different account, the popup can quietly use that identity instead of prompting fresh, and you'll get an error saying the account chosen doesn't match. Use an incognito window and pick "use another account" if that happens. Then confirm the session actually connected before doing anything else:

Get-ConnectionInformation

If that's empty, none of the Exchange Online cmdlets will work, including Set-OrganizationConfig. It'll just say the cmdlet isn't recognized, which looks like a missing module but is really a failed connection.

Check the mailbox exists, then flip the switch:

Get-Mailbox -Identity SyncTest01
Set-Mailbox -Identity SyncTest01 -IsExchangeCloudManaged $true

Testing With Provision on Demand





This is where most of the real troubleshooting happens. A single "skipped, not in scope" result can hide more than one cause. My test returned three flags at once: IsActive false, Assigned to the application false, IsInProvisioningScope true.

It's tempting to chase the wrong one. Here's what each actually tells you:

IsInProvisioningScope true means the object already passes the scoping filter. If your scoping attributes look off in the Import step, don't assume that's the problem if this already reads true.

Assigned to the application false is the one that actually blocks things. If your app is set to sync only assigned users and groups, you need to add the user under Users and groups on that Enterprise Application. Scoping filters alone won't cover this.

IsActive false can lag behind what the portal shows under account status. Don't assume it's fixed just because Entra ID shows Enabled. Check attribute mapping to see what source attribute actually feeds it.

Fix one thing at a time and re run the test. It's easy to blame the wrong flag when three of them show up together.

Note - ensure the user has an active license assigned.

Should You Turn This On Tenant Wide

There's a shortcut that makes cloud managed the default for every new mailbox:

Set-OrganizationConfig -ExchangeAttributesCloudManagedByDefault

Don't reach for this early. Microsoft only supports it once every on prem mailbox has already moved to Exchange Online. Turn it on too soon and any recipient still created on prem gets synced up as a plain user with no Exchange attributes at all. No mailbox gets provisioned, onboarding breaks, and there's no easy way back without Microsoft Support. Stick to flipping the switch per mailbox until your last Exchange server is actually going away.


Wednesday, August 19, 2026

Entra ID Temporary Access Pass (TAP) Delivery method for non Supported IGA tools

Standard

 Anytime you need to bring a batch of new users into a tenant at once a large new-hire cohort, a bulk migration, a group of external users who need day-one access you run into the same identity problem: how do you get everyone a usable credential securely, without a static password sitting in someone's inbox?

This post explains how I automated the process with Microsoft Entra ID Temporary Access Pass (TAP), Microsoft Graph, and Power Automate. My requirement is bulk onboarding, but it can also serve as the joiner trigger in the IGA process for general onboarding.

The problem

When a batch of new accounts lands in your Entra ID tenant, those people have zero trust relationship with your organization yet. You don't have their passwords, you can't assume their personal email is secure, and you generally don't know if their mobile number has been verified by anyone. So how do you get them a usable credential? 

The answer most identity teams land on is a Temporary Access Pass (TAP)  a time-boxed, single-use code that lets someone sign in and register their real credentials (Passkey, Authenticator, Windows Hello for Business) without ever touching a static password.

Additionally, if your organization has started rolling out phishing-resistant MFA, this is the best way to onboard new joiners and enforce conditional access so MFA is used only for phishing-resistant MFA. For the conditional access part, I’ll add a link at the bottom there.

The remaining problem is delivery. If you email someone their TAP and their sign-in instructions in the same message, you've just made one compromised mailbox equivalent to a compromised account. So the design principle I built around was simple:

Never put the credential and the instructions in the same channel.

The architecture

The pipeline has four moving parts:

  • A Microsoft Entra ID app registration — a scoped service identity that calls Microsoft Graph on the flow's behalf
  • Azure Key Vault — holds the app's client secret so it never sits in plaintext inside a flow definition
  • A SharePoint list — the new-user roster, which also doubles as an audit log (I’m using this because of limitations in my test environment, where Workday or the IGA tool isn’t provisioned.)
  • A Power Automate flow — the orchestrator

The sequence, once a new user row lands in SharePoint: For the test environment, SharePoint is currently the trigger point, but based on the requirements, we can move it to either Workday or an API connection.

  1. Get an OAuth token for Microsoft Graph (client credentials flow, secret pulled live from Key Vault)
  2. Resolve the user's Entra ID object ID from their UPN
  3. Generate a TAP for that user — single-use, capped lifetime, and optionally a delayed start time matched to their actual start date
  4. Send an email immediately with sign-in instructions. Additionally, we can arrange for an SMS connector and TAP to be delivered.
  5. Wait a few minutes
  6. Log the outcome back to the SharePoint row


 Prerequisites:

  • Azure Subscription
  • Power Automate Licensing for premium connectors
  • [Optional] SMS Gateway Subscription

Step by Step Configuration 

Step 1: App registration for Graph API access

  • Go to Entra ID > App registrations > New registration
  • Name it something like TAP-Automation-Onboarding
  • API permissions needed: UserAuthenticationMethod.ReadWrite.All, User.Read.All (application, not delegated, since this runs unattended)
  • Get admin consent

  • Go to Certificates & secrets and generate the New Client Secret
  • Note down the Value
  • go to Overview and note down the Client ID and Tenant ID as well 


Step 2: Trigger

  • Create a SharePoint Site and SharePoint List. List should content blow columns
    • ID Rename with UPN
    • Name - Single line of text
    • PersonalEmail - Single line of text
    • JoinDate - Date and Time
    • MobileNo - Single line of text
    • Status - Single line of text
    • TAP Generated - Date and Time
    • Email Sent - Yes/No
    • SMS Sent - Yes/No

Step 3: Create the flow

  • Go to make.powerautomate.com
  • Solutions (if you're using a solution for this project, recommended for ALM/deployment tracking) or My flows
  • New flow > Automated cloud flow
  • Name: Acquisition-TAP-Onboarding
  • Trigger: search for your list source, e.g. "When an item is created" (SharePoint) or "When a row is added" (Dataverse/Excel)
  • Configure the trigger's Site Address/List Name (or table)

  • Step 4: Initialize variables

    Add seven Initialize variable actions in sequence (click + > Add an action > search "Initialize variable" each time):
    NameTypeValue (set now or later from trigger)
    varUPNString                    dynamic content: trigger's email/UPN field
    varMobileString                    dynamic content: trigger's mobile field
    varAccessTokenString                    leave blank
    varTAPString                    leave blank
    varUserIdString                    leave blank
    varRecipientEmailString                    dynamic content: trigger's email field 
    varJoinDate           String                    leave blank

    Rename each action title via three dots (⋮) > Rename, e.g. Set varUPN, so the canvas is readable.

    Example:

    Step 5: Create an Azure Vault

    • Go to portal.azure.com
    • Subscriptions > open your subscription > Access control (IAM) > Add > Add role assignment
    • Search for Key Vault Secrets Officer, assign it to your own account (lets you manage secrets in any vault under this subscription)
    • Search for Key Vault in the top search bar > Create
    • Name: kv-tap-onboarding-wiley
    • Choose the correct subscription, resource group, and region > Review + create
    • Once deployed, open kv-tap-onboarding-wiley > Access control (IAM) > Add > Add role assignment
    • Search for Key Vault Secrets User, assign it to your automation app's service principal (search for TAP-Automation-Onboarding)

    Part 6: Get the client secret from Key Vault

    1. Go back to 
    2. + > Add an action > search Azure Key Vault
    3. Select Get secret
    4. If prompted to create a connection:
      • Connection name: Azure Key Vault - Get secret
      • Authentication type: Service Principal (Client Secret) — not Client Certificate Auth
      • Vault name: your vault name
      • Client ID: your app registration's Application (client) ID
      • Tenant: your tenant ID
      • Client Secret: paste the client secret value directly here (this one exception is fine, as discussed)
      • Click Create
    5. In the action body, Name of the secret: type TAP-Automation-ClientSecret exactly as named in the vault. (This is what we have created on step 5)
    6. Rename action: Get Client Secret

    Save.

    Part 7: Get an OAuth token from Microsoft Entra ID

    1. + > Add an action > search HTTP, filter to Built-in if both connector and built-in versions show up, select the plain HTTP action (icon is a simple shape, no service logo)
    2. Method: POST
    3. URI:
    https://login.microsoftonline.com/YOUR-TENANT-ID/oauth2/v2.0/token

    (Replace with Tenant ID)
    4. Headers: Key Content-Type, Value application/x-www-form-urlencoded
    5. Body: type this, then insert the dynamic token where indicated:

    grant_type=client_credentials&client_id=YOUR-APP-CLIENT-ID&client_secret=

    Click right after client_secret=, press /, select Get Client Secret > value. Then continue typing after the inserted token:

    &scope=https://graph.microsoft.com/.default
    1. Rename action: Get OAuth Token

    Save.

    Part 8: Parse the token response

    1. + > Add an action > search Parse JSON (Data Operations, built-in)
    2. Content: click in, press /, select Get OAuth Token > Body
    3. Schema: click "Use sample payload to generate schema", paste:
    json
    {
        "type": "object",
        "properties": {
            "token_type": {
                "type": "string"
            },
            "expires_in": {
                "type": "integer"
            },
            "ext_expires_in": {
                "type": "integer"
            },
            "access_token": {
                "type": "string"
            }
        }
    }
    1. Click Done
    2. Rename action: Parse Token Response

    Save.


    Part 9: Store the access token

    1. + > Add an action > search Set variable
    2. Name: select varAccessToken
    3. Value: click in, press /, select access_token under Parse Token Response's outputs
    4. Rename action: Set varAccessToken

    Save.

    Part 10: Look up the acquired user's object ID in Entra ID

    1. + > Add an action > search HTTP (built-in again)
    2. Method: GET
    3. URI:
    https://graph.microsoft.com/v1.0/users/@{variables('varUPN')}

    (insert varUPN via dynamic content/expression inside the URI field)
    4. Headers: Key Authorization, Value: type Bearer then insert varAccessToken dynamic content right after the space
    5. Rename action: Get User Object ID

    Save.

    Part 11: Parse the user lookup response

    1. + > Add action > Parse JSON
    2. Content: Get User Object ID > Body
    3. Schema: sample:
    json
    {
        "type": "object",
        "properties": {
            "id": {
                "type": "string"
            },
            "userPrincipalName": {
                "type": "string"
            }
        }
    }
    1. Rename: Parse User Lookup

    Save.

    Part 12: Store the user's object ID

    1. + > Add action > Set variable
    2. Name: varUserId
    3. Value: id from Parse User Lookup's outputs
    4. Rename: Set varUserId

    Save.

    Part 13: Generate the TAP

    1. + > Add action > HTTP (built-in)
    2. Method: POST
    3. URI:
    https://graph.microsoft.com/v1.0/users/@{variables('varUserId')}/authentication/temporaryAccessPassMethods
    1. Headers:
      • Authorization: Bearer + varAccessToken dynamic content
      • Content-Type: application/json
    2. Body:
    json
    {
      "lifetimeInMinutes": 480,
      "isUsableOnce": true,
      "startDateTime": "@{variables('varJoinDate')}"
    }
    1. Rename: Generate TAP

    Save.

    Note: Lifetimeinminutes follows the default TAP life time. In my environment, it is 8hr.

    Part 14: Parse the user lookup response

    1. + > Add action > Parse JSON
    2. Content: Generate TAP > Body > Body
    3. Schema: sample:
    json
    {
        "type": "object",
        "properties": {
            "id": {
                "type": "string"
            },
            "isUsable": {
                "type": "boolean"
            },
            "methodUsabilityReason": {
                "type": "string"
            },
            "temporaryAccessPass": {
                "type": "string"
            },
            "createdDateTime": {
                "type": "string"
            },
            "startDateTime": {
                "type": "string"
            },
            "lifetimeInMinutes": {
                "type": "integer"
            },
            "isUsableOnce": {
                "type": "boolean"
            }
        }
    }
    1. Rename: Parse User Lookup

    Save.

    Part 15: Store the TAP Value

    1. + > Add action > Set variable
    2. Name: varTAP
    3. Value: id from Parse Tap Response outputs
    4. Rename: Set varTAP

    Part 16: Send the email leg 

    1. + > Add action > search Send an email (V2) (Outlook connector)
    2. To: varRecipientEmail or trigger's email field
    3. Subject: Your XXX account is ready
    4. Body:
    Hello,
    
    Your account has been created: [insert varUPN dynamic content]
    
    Sign in at: https://myaccount.microsoft.com
    
    You will receive a one-time access code via SMS shortly. Do not share this code with anyone, including IT staff.
    
    Regards,
    IT Security
    1. Rename: Send Email Instructions

    Save.

    Part 17: Delay

    1. + > Add action > Delay
    2. Count: 5, Unit: Minute

    Save.

    Part 18: Send the SMS leg (TAP only)

    Requires an Azure Communication Services connection with a provisioned phone number, we haven't built this yet. When ready:

    1. + > Add action > search Azure Communication Services or Send SMS
    2. Set up connection with your ACS connection string
    3. From: your ACS number
    4. To: varMobile dynamic content
    5. Message: Your Wiley access code: + varTAP dynamic content + . Valid 24 hours, single use only.
    6. Rename: Send SMS TAP

    Save.

    Part 19: Log the outcome

    1. + > Add action > Update item (SharePoint) or Update a row (Dataverse), targeting the same row
    2. Set fields for status/timestamp

    Save.

    In the SharePoint columns we created for status, the SMS (Yes/No) and Email (Yes/No) fields will be filled in by this flow.

    Click the Test button on the top and do a manual test. once you update the sharepoint list this should send a email notification. for the email please check the Junk as if you are sending to your personal email ID. these can be flag as spam. 


    NOTE:

    In my scenario, I have built up to part 16 only. As per our requirement, we will deliver this to the hiring manager, who will verify it and share the information with the candidate. Additionally, for bulk onboarding, we only use the personal email ID as the acquisition email ID, so both the TAP and the email ID are delivered through email. 

    Additionally, for SMS, you need to provision Azure Communication Services first, or use another connector that supports SMS. Both options require a paid subscription to send SMS, which I have not covered here since my organization's requirement is only to send email at this moment. However, I believe SMS is the better way to share the TAP.

    Endless possibilities

    • Rather than triggering from SharePoint, there are connectors available for Workday or HTTP APIs to trigger from your organization's source of truth or IGA tool. You only need to change the trigger, and the rest of the workflow can remain the same.
    • For delivering to hiring managers, you can get a variable to set the end user's manager and share this with the manager as well.
    • SMS or secure link delivery methods can be integrated based on your requirement.


    Friday, December 5, 2025

    Azure PIM rollout with best practices

    Standard

    Common Roles and Responsibilities

    Azure role

                   Permissions

    Owner

    ·       Grants full access to manage all resources

    ·       Assign roles in Azure RBAC

    Contributor

    ·       Grants full access to manage all resources

    ·       Can't assign roles in Azure RBAC

    ·       Can't manage assignments in Azure Blueprints or share image galleries

    Reader

    ·       View all resources but does not allow you to make any changes.

    Role Based Access Control Administrator

    ·       Manage user access to Azure resources

    ·       Assign roles in Azure RBAC

    ·       Assign themselves or others the Owner role

    ·       Can't manage access using other ways, such as Azure Policy

    User Access Administrator

    ·       Manage user access to Azure resources

    ·       Assign roles in Azure RBAC

    ·       Assign themselves or others the Owner role

    Azure built-in roles - Azure RBAC | Microsoft Learn

    To create a subscription or billing profile, you need to have either the Account Admin or Enterprise Admin role. These roles are assigned directly and are not managed through PIM. Users with these roles are responsible for creating subscriptions and managing billing profiles. IAM Emergency access account will be added to Enterprise administrator role to as a recovery account. In situations where old employee is left and new employees need access; these emergency accounts could be used for recovery. 

    below diagram shows how we can grant Access using PIM.


    As indicated above, the basic method for managing permissions is to create and manage them using a Security group.

    Propose a plan to manage the management root. This permission applies to all subscriptions and resource groups. According to Microsoft best practices, the number of management root owners should not exceed three.
    Management Group permission management 

    This method ensures that only the IAM team can grant access to resources. If a task requires the Owner role for the Cloud team, they can obtain it through an approval workflow.

    Tips:

    • After the Enterprise admin creates a subscription, the user will automatically become the owner. Once the task is assigned to the IAM team, they should remove the previously added owner, as access will be properly inherited from the management group.
    • Alerts should be set up to notify multiple teams to ensure complete visibility on role enablement. We can also assign the SOC team to monitor these alerts.
    • Regarding the owner approval workflow, we can assign additional approvals if we need to accommodate multiple time zones and availability.
    • Even if you are listed on the approval list, you cannot approve your own request; it must be approved by someone else. Both the requester and the approver are required to add a justification as well.