Three directories, one source of truth.
Here's what I learned building it.
I've worked quite a bit with identity management, particularly Entra ID, SSO, and automated user provisioning. Most of that experience has been within Microsoft environments, and while I'm a big fan, I wanted to expose myself to some other tools and workflows. Luckily, IAM transfers conceptually, and each interface is just a different array of buttons.
So I built an environment where Okta is the source of truth for both Microsoft Entra ID and Google Cloud Identity. I didn't want to stop at getting a couple of SSO buttons working. I wanted to test the actual identity lifecycle: provisioning users, updating attributes, enforcing authentication policies, suspending accounts, and making sure the results were consistent across all three directories. Because why not go for gold?
I also wanted to see what kind of issues could arise from that setup e.g. What if a user already existed in one directory? What if the usernames didn't match? What happens if Okta goes down? And, most importantly, does disabling a user in Okta actually disable them in both downstream directories?
The environment runs entirely on free tiers: Okta Integrator Free, Entra ID Free, and Google Cloud Identity Free. So there were some limits with things like Conditional Access, PIM, and logging, but constraints are where creativity flourishes.
In the end, I got the provisioning, authentication, and deactivation flows working and verified them in each directory. The interesting part wasn't just that they worked. It was finding out how differently Entra and Google handle what looks like the same integration on paper.
One thing I didn't expect was how much access the standard provisioning connectors requested. Both integrations can do far more than create user accounts, which makes the connectors themselves part of the security boundary. That was very notable as a possible oversight, in my opinion.
[ READ MORE — THE BUILD, FAILURES & RESULTS ▼ ][ SHOW LESS ▲ ]
The architecture
The overall design is pretty straightforward. Okta owns the identity, provisions it into the other directories, and handles the authentication requirements.
The distinction I had to keep in mind was that provisioning and authentication are separate operations. Provisioning decides whether an account exists, what attributes it has, and whether it's enabled. Federation decides how that account signs in. One can work while the other is completely broken.
I also manage the Okta groups and authentication policies with Terraform, in okta-as-code, including plan-on-PR, apply-on-merge, and a daily drift check. I left the Microsoft and Google connectors out of this part. They carry authenticated tokens, consent grants, and provisioning state that I don't want an incomplete provider changing or replacing.
Getting identities to match
I went with a simple identity model: one person, one lowercase username, and the same address in all three directories. But, just for fun, some of them use random, capitalized letters. Mostly so I can try applying the transformation String.toLowerCase(user.login) to the integrations.
Entra uses userPrincipalName to match an existing account. Google uses primaryEmail. I also learned that I could verify glasscliff.online in Entra and add it to Google as a secondary domain. I assumed I would and bought a second domain ahead of time. It's in my back pocket for now, though.
Of course, I managed to demonstrate why matching matters almost immediately.
One account in Entra differed from its Okta counterpart by one letter. Instead of linking the account, Okta created another one. I'm going to take credit and say that was all according to plan. Just an intentional way to see exactly what would happen... Haha... ha... On purpose...
There's also a difference in how the vendors anchor identities. In this configuration, Okta placed its identifier into Entra's onPremisesImmutableId, which was interesting, but I didn't bother trying to change it despite having a hybrid homelab. No problem in seeing how that affects on-prem identities. Google's connector matches by email, without an equivalent exposed immutable anchor. So I don't think I can casually delete and recreate the Okta user and reattach it to the existing Entra account.
Okta to Microsoft Entra ID
On the Microsoft side, I used Okta's Office 365 integration with Create, Update, and Deactivate enabled. Provisioning goes through Microsoft Graph, password synchronization is off. As an aside, I was very happy to see that the free tenants actually have everything I'd need.
Authorizing the connector created the Okta Microsoft Graph Client service principal in Entra with its own set of application permissions. That's useful operationally, but the permissions themselves are worth looking at:
RoleManagement.ReadWrite.Directory
AppRoleAssignment.ReadWrite.All
Application.ReadWrite.All
User.ReadWrite.All
Group.ReadWrite.All
GroupMember.ReadWrite.All
Organization.Read.All
Application.Read.All
A few of those are highly privileged. In particular, I was looking at RoleManagement.ReadWrite.Directory. That role is capable of assigning directory roles, including Global Administrator. That's a looooot of standing access for a user provisioning connector. Not an issue here, just an eyebrow raiser.
Microsoft's consent prompt doesn't offer individual permission checkboxes. Reducing that privilege is a separate hardening thing that I still want to test.
Federating the domain
For authentication, I configured WS-Federation. Okta used its Graph authorization to change the authentication type of glasscliff.online from Managed to Federated. From that point on, users on the federated domain are redirected to Okta to authenticate.
Before that, though, I tested my break glass account for my own anxiety. I verified the result through Graph instead of the GUI. Okta also configured federatedIdpMfaBehavior as enforceMfaByFederatedIdp, so Entra accepts MFA performed upstream by Okta.
The signing certificate was another detail I checked. Mine had a ten-year validity period, but nextSigningCertificate was null. More immediately, the certificate is associated with the Okta app instance. Deleting and recreating that instance can break the trust long before its certificate expires.
Okta to Google Cloud Identity
The Google connection uses its Admin SDK Directory API for provisioning and SAML 2.0 for authentication. The basic goal is the same, but the controls and defaults are very different.
I enabled Create, Update, and Deactivate. I also disabled inbound imports. If Okta is supposed to own the identities, I don't want my Google service and emergency accounts being sent over to Okta. I only have ten seats on there!
Organizational units actually affect authentication
Google uses organizational units as an important scope boundary. I created separate OUs for Okta-provisioned users, administrators, service accounts, and breakglass.
| Google OU | SSO assignment |
|---|---|
/Okta Users | Okta SAML profile |
/Administrators | None — Google sign-in |
/Service Accounts | None — Google sign-in |
/Breakglass | None — Google sign-in |
I also had to adjust Google's post-SSO verification setting for the Okta users. Without that, Google could accept the SAML response and then demand a phone number. Which would be fine if I had ten phones, but I do not. Entra's federation setup already trusted the MFA handled by Okta, Google needed an additional OU-level change.
Attribute mapping can remove data
For one soft-match test, I created a Google account with slightly different values than its Okta counterpart, then pushed the identity from Okta. The account linked successfully without creating a duplicate and the attribute results were corrected:
| Attribute | Before | After push |
|---|---|---|
| First name | Durel | Durell |
| Job title | Soldier | Empty |
| Department | First Infantry | Empty |
| OU | / | /Okta Users |
The first name was corrected and the OU changed as expected. The other two fields were cleared, because Okta didn't have a title or department value for that user and I'd configured those fields for updates.
Enabling Update User Attributes also changed existing mappings from Create to Create and Update in one move. That's something I would review carefully before turning on in an established environment.
Google's connector also requested substantial OAuth scopes, including admin.directory.rolemanagement and admin.directory.user. In combination, those can provide a route to full tenant control. But unlike Entra, Google Workspace has per-scope consent checkboxes, so permission reduction is at least directly testable.
Planning for Okta being unavailable
Centralizing authentication also creates a central dependency. Being locked out an entire tenant scares me, I've heard many horror stories regarding it. If Okta is misconfigured, compromised, or unavailable, I still need a way to log into Entra and Google Workspace.
For Entra, I created a cloud-only emergency Global Administrator on the original .onmicrosoft.com domain, which isn't federated. It has independent credentials and MFA. I ensured to grant this account full subscription management access as well; tenant recovery and subscription recovery are separate steps.
For Google, I created a super administrator outside the federated OU, with independent authentication and offline recovery materials.
Google complicated the account's first sign-in with its login challenge demanding a phone number (as mentioned before. This is actually the part that made me disable it). Password resets didn't solve it. I used my primary super administrator to temporarily turn off the login challenge long enough to initialize the account until I found out how to disable it entirely.
I keep them separate from the accounts used to authorize the connectors. In a live environment, these accounts would need to be tested quarterly and around federation changes.
Things that broke during testing
An Okta app tile isn't proof of federation
The Google app initially used Secure Web Authentication, which can store and replay a password. From the user's seat, clicking the tile can look like SSO even though it isn't SAML federation. I explicitly switched to SAML and verified the target's SSO configuration instead of treating a successful tile launch as evidence.
Microsoft returned AADSTS90019
Launching Microsoft from the Okta dashboard returned AADSTS90019. Okta's logs showed successful policy evaluation and an SSO response. Microsoft was rejecting the IdP-initiated request because the expected tenant context was missing.
Starting at Microsoft's login page and letting home realm discovery redirect to Okta worked immediately. That told me the federation itself was functional (relieved) and the problem was in the launch path, not a reason to rebuild the trust.
Okta rejected a valid Google organizational unit
After changes to Google's OUs, provisioning occasionally produced an error including:
Invalid data value for REFERENCE 'orgUnitPath'
(type=GoogleOrganizationUnit)
It appeared related to Okta's cached Google OU references because running Force Sync correlated with the issue clearing twice, though I haven't fully verified that it was the mechanism refreshing the cache.
Google's reporting error pointed at DNS
Google told me a report wasn't available because my domain might not be verified or the edition might not support it. The domain was verified; the free edition simply didn't include that reporting feature :(.
User status wasn't a reliable authentication indicator
I also found users with passkeys could authenticate while Okta showed their password status as PASSWORD_EXPIRED. Although this ended up being a misconfigured authentication policy, I did note that a password related status doesn't necessarily tell you whether a user is in a working state. That's important for any reconciliation dashboard I might build later.
Does deprovisioning actually work?
This was the test that mattered most to me. I deactivated a user in Okta, then checked both downstream directories independently.
| Directory | Observed state |
|---|---|
| Okta | DEPROVISIONED |
| Entra ID | accountEnabled: false |
| Account suspended |
One action in Okta disabled the identity in both downstream directories. Neither user object was deleted, and the existing identifiers were retained. Success on the first try!
Rollback needs more than a switch
Another thing I wanted to understand was what rollback would actually look like if federation failed.
Simply removing SSO doesn't restore users to a known, good state. Okta-created users can have passwords generated during provisioning that nobody knows, and I've disabled password sync. Remove federation and those users might have no usable local credential.
For an actual rollback, I'd need to remove the SSO assignment or federation and reset the affected users' passwords, distribute credentials out of band, and restore the appropriate verification requirements.
Provisioning rollback is simpler: disable the connector operations or revoke authorization while keeping existing accounts in place.
What I'm still working on
The main flows are working, but this isn't a claim that every edge case or security control has been completed. The most important remaining task is reducing the standing privileges on both provisioning connectors and verifying that the integrations continue to work.
Build reconciliation across Okta, Entra, and Google.Done since this was written: a twice-daily, read-only reconciliation.- Test whether an Okta Suspend event propagates the same way as Deactivate.
- Determine whether Google's downstream password is regenerated on every profile push or just at creation.
- Import the groups, authentication policies, and management into Terraform. As it stands, I currently have Terraform managing another policy. I plan to implement this for these as well as a .5 addition to this project by the end of the week.
- Test connector scope reduction.
- Find a way to monitor and alert for Breakglass events (if the free tiers can handle that.)
I may have to buy licensing just to play around with more settings. No Entra P1 means no Conditional Access, PIM, or the diagnostic export options I'd use in a licensed enterprise tenant. Google Cloud Identity Free also limits reporting.
What I took away from the project
In theory, this is a simple architecture: Okta creates and manages identities in two directories, and both directories trust it to authenticate their users.
What made it useful was everything around that: Entra and Google disagree about identifiers, identity anchors, permission consent, federation defaults, and post-SSO challenges. I encountered destructive attribute updates, a duplicate identity, a misleading application tile, a federation error isolated to IdP-initiated sign-in, and a rollback path that could have left everybody without working credentials.
I ended up with something functional: I can create and manage users in Okta, provision them into Entra and Google, require phishing-resistant authentication, and disable the identities from one place.
More importantly, I have a better picture of the trust boundaries, what I'd have to account for before doing something similar in production, and inter-platform identity management. And that's all I really wanted out of it.
Built and verified 10/09/26 - 10/12/26. Additional hardening and edge case testing to come.