Managing external identities to enable secure access for partners, customers, and other non-employees
Hello Jinki Lee ,
Greetings! Thanks for raising this question in the Q&A forum.
Your instinct to use a Function App keyed off the App Registration Client ID is exactly the right approach, and it maps directly onto a built-in capability rather than something you need to bolt on separately. Instead of triggering the Function App as a separate step after sign-in completes, hook it into the sign-in flow itself using a custom authentication extension on the OnTokenIssuanceStart event. This event fires for every sign-in, and its payload already includes the client application's App ID (under clientServicePrincipal.appId) and the user's object ID, which is exactly what you need to determine which business unit group the user should belong to, with no separate post-login call or extra latency for the user.
- Create the Azure Function that will act as your custom claims provider REST API
The function receives the OnTokenIssuanceStart payload, reads data.authenticationContext.clientServicePrincipal.appId to determine which app the user is signing into, maps that to the correct security group ID (abc.domain.com app registration's Client ID maps to the "abc" group, xyz.domain.com's maps to the "xyz" group), and then calls Microsoft Graph to add the user to that group if they are not already a member.
- Add the group membership call using Microsoft Graph inside the function
Use application permissions (client credentials) since this runs server-side with no user context:
POST https://graph.microsoft.com/v1.0/groups/{groupId}/members/$ref
Content-Type: application/json
{
"@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/{userId}"
}
Check membership first (or catch the duplicate-member error and ignore it) so repeat sign-ins do not fail the extension:
GET https://graph.microsoft.com/v1.0/users/{userId}/memberOf/microsoft.graph.group?$filter=id eq '{groupId}'
- Register the Function App as the API Authentication app for the extension and grant it Graph permissions
The app registration created during the custom authentication extension setup (used to secure calls to your Function) needs GroupMember.ReadWrite.All as an application permission with admin consent, in addition to the CustomAuthenticationExtension.ReceivePayload permission that gets granted automatically when you click Grant permission during setup.
- Register the custom authentication extension for the TokenIssuanceStart event
In the External ID tenant, go to Entra ID > External Identities > Custom authentication extensions > Create a custom extension, select TokenIssuanceStart, and point it at your Function's URL. Do this once, since a single custom claims provider can serve multiple applications, your function logic differentiates behavior per app using the client App ID from the payload rather than needing a separate extension per app.
- Attach the custom claims provider to both applications
For each of your two app registrations (abc and xyz), go to the corresponding enterprise application's Single sign-on > Attributes & Claims > Advanced settings > Custom claims provider, and select the extension you created. This ensures the OnTokenIssuanceStart event fires, and your function runs, regardless of which of the two apps the customer signs into.
- Handle the users who sign into both apps naturally, no special logic needed
Since the function runs independently on every sign-in and simply adds to a group if not already present, a customer who signs into both abc.domain.com and xyz.domain.com over time will end up in both groups automatically the first time they authenticate through each app, with no extra code required to handle that case.
- Keep the function fast and idempotent
Custom authentication extensions have a short response time budget (a few seconds) before Entra ID times out the call and proceeds without the extension's changes, so keep the Graph calls minimal, use a single application-permission-based Graph client that is reused across invocations rather than re-authenticating each time, and make sure your logic tolerates being called multiple times for the same user without erroring.
If this answer helps you kindly accept the answer which will help others who have similar questions.
Best Regards,
Jerald Felix.