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.


  1. 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).

  2. 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).

  3. 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.

  4. 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.

  5. 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_success events (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 show CREATE_APPLICATION_SETTING or CHANGE_APPLICATION_SETTING, including which organizational unit or group the app was turned on for.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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 state value. state is 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.

  11. 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: 1 for member, 8 for admin, 15 for owner. The REST docs don’t explain the values. They’re named in the OrganizationMembership.Level enum in PostHog’s source.

  12. 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, not memberships.read, and memberships.write can’t change group membership.

  13. 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.

  14. Linear

    Linear’s GraphQL error format is only documented in its SDK

    The shape of Linear’s GraphQL errors, including the type, code, and userPresentableMessage extensions, 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 as authentication error, not the AuthenticationError value from the SDK’s own enum.

  15. 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.

  16. 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 roles field. Though you can set (just) the org admin role through SCIM.

    Roles themselves, other than org admin, can’t be configured via API.

  17. 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.

  18. 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-Resource header says which one a response counted against: core for REST, graphql for GraphQL. A rate-limited GraphQL query still returns HTTP 200, with a RATE_LIMITED error in the body.

  19. 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.

  20. GitHub

    GitHub’s GraphQL API doesn’t return organization members’ 2FA status to apps

    OrganizationMemberEdge.hasTwoFactorEnabled is documented as null “if information is not available to viewer”. To a GitHub App installation, it’s null for every member, with a FORBIDDEN “Resource not accessible by integration” error, whatever permissions the app holds. The workaround is the REST members and outside collaborators endpoints with filter=2fa_disabled, and inferring everyone else has it enabled.

  21. 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_id query parameter, alongside the OAuth code. 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 the code parameter (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 in GET /user/installations.

  22. 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.

  23. 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.

  24. 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.

  25. 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 token audit source and look for authorize events to see when a user granted access. Those are only available for the last 180 days.

  26. 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.

  27. Okta

    Okta’s userType and user types are unrelated

    Okta has two different things called “user type.” There’s the userType attribute, 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’s userType attribute 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 userType attribute in a group rule.

  28. 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.

  29. 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.

  30. 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.

  31. 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).

  32. 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.)

  33. 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.

  34. 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.

  35. 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.

  36. 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.

  37. 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.