IAM quirks
Every app does identity a little differently. Here are the quirks we’ve hit while building Oblique’s integrations.
Even with standards like OIDC, SAML, and SCIM, how each app handles identity is slightly different. Some apps let you add users directly, while others only let you invite them. And some apps have nested resources, custom roles (or multiple roles, or both!), and usernames that aren’t emails.
Everyone building an app has a reason for doing it their way, and having built enterprise features ourselves, we know there’s usually a good reason for an odd API decision (or at the very least, a lesson learned for next time). This is a list of what Oblique handles for you, so it isn’t your problem.
-
Google Workspace
You can’t link to Google Workspace’s SAML logs
Google Workspace provides admins with a report with logs for user logins to SAML apps. You can access it under Reporting > Audit and investigation > SAML log events. The official URL isn’t linkable, and you can’t link to a filtered view of that report (for, say, a single app or user), but an unofficial URL seems to work (
https://admin.google.com/ac/reporting/audit/saml). -
Gong
Gong’s daily API rate limits are quite low
Gong’s API rate limits are 10,000 API calls per day (~7 requests per minute).
-
Notion
You can’t link to Notion’s members list
Notion has a settings page where you can manage the members in your workspace and their roles, but you can’t link to it directly because it’s a dialog. You need to manually open it in the app. However, Notion does have a People Directory page that lists everyone in your workspace, and that you can link to… but it doesn’t have roles, and you can’t edit anything from it.
-
1Password
1Password requires an account ID, but doesn’t provide it anywhere in its UI
All 1Password Users APIs require an account ID parameter, but this isn’t available anywhere in its UI. The easiest way to determine this is to visit your vault items, and copy the 26 characters after
/app#/in the URL address. -
Stripe
Stripe roles can’t be set via API
Stripe doesn’t let you set roles through its API. You can set roles in Stripe from the Stripe dashboard, or through your identity provider, either as custom SAML attributes at sign-in, or through Stripe querying your IdP’s SCIM endpoints.
-
Google Workspace
Google Workspace’s SAML apps don’t have an API
Unlike OAuth apps in Google Workspace, there’s no API for the SAML apps configured in Google Workspace, or who is assigned those apps. If you’re trying to figure that out, your best bet is parsing the logs from the Reports API. SAML
login_successevents (applicationName=saml) tell you who signed in to which app, but only if they did in the last 180 days.If you’re lucky, the app was set up or changed in the last 180 days, and admin events (
applicationName=admin) will showCREATE_APPLICATION_SETTINGorCHANGE_APPLICATION_SETTING, including which organizational unit or group the app was turned on for. -
Cloudflare
An integration authenticated to Cloudflare as a user can’t change that user’s access
If you connect a third-party OAuth 2.0 integration to Cloudflare, then the returned credentials can’t change the access of whoever authorized the integration. Though that makes sense to keep the integration from breaking itself.
-
Render
Render API keys are scoped to a user, not a workspace
An API key has the access of the user who created it, across every workspace they belong to. There’s no way to create a key limited to a single workspace.
-
Notion
Connecting a Notion integration requires you to pick pages
When you connect an OAuth integration to Notion, it assumes you want to read content saved in Notion, so you click through a consent screen that asks which pages to share, even if you don’t need access to any pages. Notion’s OAuth doesn’t take a scope parameter. To get access to a list of people in the workspace, grant access to System > People.
-
Slack
Slack’s OAuth scopes are comma-separated
When Slack’s OAuth flow returns the scopes a token was granted, it separates them with commas (
channels:read,users:read), as opposed to the OAuth 2.0 spec which specifies space-separated values. A standard client reads Slack’s returned list as a single scope that matches nothing it asked for. We still appreciate Slack returning the granted scopes in its response. -
Ramp
Ramp’s developer console won’t build an authorization URL without a state value
The authorization URL builder for a Ramp app requires a
statevalue.stateis meant to be unique to each authorization request, so the app can verify it on the callback and reject forged ones. A single value typed into a form can’t do that. -
PostHog
PostHog’s role values are only documented in its source code
The API reports an organization member’s level as the integer PostHog stores:
1for member,8for admin,15for owner. The REST docs don’t explain the values. They’re named in theOrganizationMembership.Levelenum in PostHog’s source. -
Cloudflare
Cloudflare doesn’t publicly document its OAuth 2.0 scopes
Cloudflare doesn’t have a public list of OAuth 2.0 scopes (though you can scrape them from the app configuration page). In addition, scope names often don’t match what they grant. Listing IAM groups needs
account-settings.read, notmemberships.read, andmemberships.writecan’t change group membership. -
Linear
Linear’s API docs are rendered client-side
Linear’s developer docs are a single-page app, so fetching a page returns an empty shell. Tools that read docs over HTTP, including coding agents, can’t see the content.
-
Linear
Linear’s GraphQL error format is only documented in its SDK
The shape of Linear’s GraphQL errors, including the
type,code, anduserPresentableMessageextensions, is only documented in the TypeScript SDK source. Even then, the SDK doesn’t match the live API. When Linear rejects an access token, it reports the error type asauthentication error, not theAuthenticationErrorvalue from the SDK’s own enum. -
Cloudflare
Cloudflare has roles that don’t appear in its list of roles
Cloudflare supports ad hoc roles: unnamed roles attached to a single member. They aren’t returned when you list the account’s roles, so they only show up on the members that hold them.
-
Figma
Figma’s SCIM support is for provisioning seats, not roles
Figma supports SCIM, but for provisioning Figma seats (licenses), not roles. Confusingly, the seat type goes in SCIM’s
rolesfield. Though you can set (just) the org admin role through SCIM.Roles themselves, other than org admin, can’t be configured via API.
-
Figma
You can accidentally add a seat when sharing a Figma file with someone outside your org
If you share a file in Figma with someone outside your company and give them edit access, they’re added to your plan as a guest and need a paid seat on your plan, even if they already pay for Figma elsewhere. Depending on your seat approval settings, that seat can be added to your bill automatically.
And removing their access to the file or deleting it doesn’t remove the seat. They stay on your plan with that paid seat until an admin downgrades or removes them.
-
GitHub
GitHub’s REST and GraphQL APIs have separate rate limits
REST and GraphQL each have their own hourly allowance per installation, and each resets on its own schedule, so spending one leaves the other untouched. The
X-RateLimit-Resourceheader says which one a response counted against:corefor REST,graphqlfor GraphQL. A rate-limited GraphQL query still returns HTTP 200, with aRATE_LIMITEDerror in the body. -
GitHub
A GitHub App can only be installed once per organization
An organization can’t have two installations of the same app — GitHub doesn’t let you install the same app again. To reinstall it, you have to uninstall it first. That means two environments (say, staging and production) that connect the same organization through one app share a single installation, and changing (or uninstalling) it for one environment affects the other.
-
GitHub
GitHub’s GraphQL API doesn’t return organization members’ 2FA status to apps
OrganizationMemberEdge.hasTwoFactorEnabledis documented as null “if information is not available to viewer”. To a GitHub App installation, it’s null for every member, with aFORBIDDEN“Resource not accessible by integration” error, whatever permissions the app holds. The workaround is the REST members and outside collaborators endpoints withfilter=2fa_disabled, and inferring everyone else has it enabled. -
GitHub
GitHub’s install redirect names the installation in a user-controlled query parameter
After someone installs a GitHub App, GitHub redirects back with the installation in an
installation_idquery parameter, alongside the OAuthcode. Nothing ties the two together, so whoever completes the flow can swap in another organization’s installation ID. This is particularly dangerous because apps derive credentials to call the GitHub API from the installation ID and not thecodeparameter (unlike “normal” OAuth 2.0 applications).
To confirm the installation actually belongs to the user, exchange the code for a user token and check that the ID appears inGET /user/installations. -
Google Workspace
Google Workspace APIs disagree on how service accounts authenticate
Some Admin SDK APIs accept calls authenticated as the service account itself. Others, like the Reports API, require domain-wide delegation, with the service account impersonating a Workspace admin. Which APIs need which isn’t documented. Called as the service account, the Reports API returns
401 Access denied. You are not authorized to read activity records.Delegation is also granted per OAuth scope in the Admin console, separately from the scopes the service account requests, and both have to line up.
-
Okta
Okta Preview doesn’t behave like Okta production
Okta Preview orgs don’t always behave the same as production orgs. A change that works in a preview org has to be tested again in production.
-
Okta
Okta’s API rate limits are per minute
Many Okta management endpoints are limited to around 500 requests per minute, not per second. There are no bulk operations, and a complete picture of one object often takes several calls, so managing a real org through the API or the Terraform provider is slow.
-
Google Workspace
You can only tell when Google Workspace users last signed into an app from audit logs
As an admin for Google Workspace, you can see which OAuth apps each user has currently granted access to, but not when they last signed in or used them. To get that information, your best bet is to parse logs from the Reports API’s
tokenaudit source and look forauthorizeevents to see when a user granted access. Those are only available for the last 180 days. -
Okta
To remove users an Okta group rule added, exclude them with another group
If you want to remove the users added to an Okta group by a group rule, you can delete the rule, and remove its members. But if the rule has been edited over time, it’s hard to predict exactly who that will remove. You might have directly added some of those users since then, and Okta only lets each user have one reason for being in that group.
If instead you remove users from the group one at a time, Okta adds them to that rule’s exclusion list, which can affect other groups the rule assigns. And a rule can only exclude 100 users anyway.
The workaround for removing users that a group rule added is to put them into a separate group, and exclude that group in the rule’s expression.
-
Okta
Okta’s userType and user types are unrelated
Okta has two different things called “user type.” There’s the
userTypeattribute, a free-text field on the user profile. And there are user types, separate profile schemas you can use to define different fields for employees and contractors. Changing a user’suserTypeattribute doesn’t change their user type, and group rules don’t run at all for users on a custom user type.To create a group of all contractors, keep them on the default user type and use the
userTypeattribute in a group rule. -
Okta
Okta’s “Block synced passkeys” blocks Touch ID in Safari
Okta’s “Block synced passkeys” setting blocks WebAuthn authenticators that are multi-device (that is, they sync between devices) or that don’t provide attestation, since Okta can’t rule out that they sync. That included Touch ID in Safari, because it uses iCloud Keychain — even as a second factor.
Update (April 2026): Okta changed how “Block synced passkeys” works to no longer check attestation. It now blocks authenticators known to sync, such as iCloud Keychain, Google Password Manager, and 1Password. Non-syncing options like Touch ID in Chrome are no longer blocked. Touch ID in Safari is still blocked. This only affects orgs who enrolled in the feature after the change.
-
Okta
Okta group push doesn’t correct drift
Okta’s Group Push pushes the membership of a group in Okta to an application. The full group is only pushed the first time, with only diffs sent afterwards. And, this is never verified — nothing ever checks that what’s in the app matches. Users added or removed directly in the app or syncs that are never completed are never reconciled.
The only way to reconcile is to hit “Push Now,” which overwrites the app’s membership with Okta’s.
-
Okta
Okta import safeguards don’t stop indirect access changes
Okta has import safeguards to keep a sync with bad HR data from breaking access. Okta’s import safeguards stop an import that would unassign too many users from apps at once. But they aren’t triggered for any indirect changes in access, like when an attribute a group rule depends on changes and the group loses access.
-
Okta
Removing a user from an Okta rule-managed group excludes them from the rule everywhere
When you use Okta group rules to grant access, manually removing a user (who was added by a group rule) doesn’t just remove them from the group, it also adds them as an exception to the rule. That exception applies everywhere the rule is used, so if the rule assigns users to other groups too, the rule stops assigning this user to those groups as well (that applies for future runs, but doesn’t change current groups).
-
Okta
To delete an app’s group through Okta, link it to a throwaway Okta group
Okta can only delete a group in a downstream app if that group is linked to a group in Okta.
To use Okta to delete a group that already exists in the app, you have to create a placeholder group in Okta, link it to the app group, and then delete the placeholder, which deletes the app group along with it. (Or, of course, just delete the group directly in the app.)
-
Okta
Okta group admins can’t see the rules that manage their groups
If a user is an admin of specific Okta groups, they can’t see the group rules that add members to those groups. Only group admins who manage all groups can view rules, and only super admins and org admins can edit them.
-
Okta
You need to deactivate an Okta group rule to edit it
If you need to edit an Okta group rule, like to change its conditions or exclusions, you first need to deactivate it. Then, you can edit it and reactivate it.
While it’s deactivated, existing members stay in the group, but the rule won’t add or remove anyone. When you reactivate it, Okta re-evaluates group membership based on the updated rule.
Did you want to change what the group rule assigns? Then delete the rule and create a new one instead.
-
Okta
You need to follow the right order when unassigning an Okta app
The order in which you take actions in Okta matters. To remove a user from an app, you have to remove them from its push groups before unassigning the app. If you unassign them first, they can stay a member of those groups in the downstream app.
-
Okta
An Okta group shouldn’t be used for both app assignment and group push
Okta doesn’t support using the same group for assigning an app and for pushing a group to it, even if both groups have the same members. It warns you about this multiple times in their documentation.
This is because in order to provision a user to a specific role in an application, you first need the user to be assigned the app and provisioned an account, and then the user to be granted a specific role — and Okta can’t guarantee this. The same problem happens with offboarding.
In practice, many folks use the same group for both anyway, or if they follow the advice, maintain two groups populated by the same rule. Even then, the push job occasionally fails and has to be rerun.
-
Okta
An Okta group rule takes over manually added members
If a user was added to a group by hand and a new group rule also matches them, their membership becomes managed by the rule. If the rule is later edited and stops matching them, the user is removed from the group, even though they were originally added manually.
Ready to stop managing access manually? Let Oblique handle it
Tell us what you're running and we'll get you set up.