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.


    0 comments:

    Post a Comment