Butterfly Security for iOS
Okta incident response, helpdesk actions, and your recovery readout, on the phone you already carry
What the App Is
A companion to the web app, scoped to the work you actually do away from a desk.
Built for
- 1.On-call incident response. Suspend a user, clear every session they hold, or flip a network zone to enforced while you are still walking to your laptop.
- 2.Helpdesk actions. Send a password reset, expire a password, unlock a locked-out account, adjust group and application membership.
- 3.A disaster-recovery readout. Resilience score out of 100, drift over the last 7 days, backup evidence, and a restore preview showing what a restore would cover.
Deliberately not in the app
- 1.Restore execution. The app shows a restore preview. Pressing the trigger stays on the web app. See the note below.
- 2.Creating connections. The app administers Okta orgs you have already connected. Add and authorize an org from the web dashboard.
- 3.Compliance, drift remediation, Terraform export, and schedule editing. Those surfaces live in the web app today. The phone shows the drift summary, not the drift workflow.
Why full restore execution stays on desktop
A full org restore can touch thousands of resources in a single run. That is the largest blast radius in the product, and it deserves a dry-run diff reviewed on a large screen, not a confirmation sheet tapped one-handed. The app gives you everything you need to decide when to restore (resilience score, drift signal, backup currency, and a per-resource preview of what the restore would cover). You execute from butterflysecurity.org. This is a design decision, not a gap in the roadmap.
Requirements
Four things, and you almost certainly have three of them already.
iPhone or iPad, iOS 17.0 or later
Universal binary. Portrait on iPhone, all orientations on iPad. Face ID, Touch ID, or a device passcode is required to authorize any change.
A Butterfly Security account
The app is a free download and is included with every plan, including the 30-day trial. Sign in with the same work email you use on the web app.
An existing Okta connection
The app administers orgs you have already connected. It does not create connections. Add your Okta org from the web dashboard first.
The manage scopes you intend to use
Read-only scopes get you the whole readout. Each mutation needs its own manage scope granted to the Okta integration, or the button stays locked.
Single sign-on is not supported in the app yet
The app signs you in with the one-time code emailed to you. It cannot yet complete a single sign-on redirect, so a workspace that enforces SSO will send you back to the sign-in screen after you pick it. If you belong to a workspace that uses SSO and one that does not, pick the non-SSO workspace on the phone and use the web app for the SSO one. SSO sign-in on iOS is in development. SCIM provisioning is unaffected: it runs server to server and keeps working regardless of how you sign in on a device.
Setting up a connection first
Connections are created by installing the Butterfly Security API Service Integration from the Okta Integration Network and authorizing it in your Okta admin console. The full walkthrough lives in the OIN API Service Integration guide. Once the connection tests clean on the web, it appears in the app.
Install and First Run
Sign-in is passwordless. There is no password to type and none to rotate.
Install from the App Store
Search for Butterfly Security, or open the listing directly from the button below. The download is free.
Read the three intro screens
A short first-launch introduction covers the recovery readout, incident-response actions, and the scope model. Tap Get started at the end. It appears once per install.
Enter your work email and tap Send sign-in code
A one-time code is emailed to you. It has the shape ABC-123. Paste or type it on the next screen. Codes normalize automatically, so case, spacing, and a missing hyphen all resolve to the form the server accepts.
Pick a workspace if you belong to more than one
If your email belongs to a single workspace, the app selects it for you and goes straight through.
Pick the Okta org to administer
The Connections screen lists every connection on the workspace with its status and last backup time. Tap one. The app remembers it across launches, and you can change it later under Settings, Switch connection.
Confirm the biometric requirement is on
Under Settings, Security, the toggle reads “Require Face ID for actions” (or Touch ID, or Optic ID, depending on the device). It is on by default and we recommend leaving it on. It is what lets the server accept your mutations at all.
Evaluating without an account
The sign-in screen has a second button, “Preview with sample data.” It opens the full app against a sample Okta org with no account and no sign-in code. Every screen and every action is exercisable, and nothing outside the sample data changes. Leave it from Settings, Sign out. Sample state is never written to disk, so relaunching returns you to the sign-in screen.
Feature Walkthrough
Four tabs: Home, Users, Backups, and More. Everything below is reachable in two taps or fewer.
Recovery readout
HomeThe first card is the resilience score for the active connection, shown as a number out of 100 with the contributing factors listed underneath. Below it: the most recent backup with its status, age, and per-resource counts; a 7-day drift summary split into total, critical, and warning changes, with the three most recent changes named; and Quick actions with Run backup and Find user.
Pull to refresh re-reads all three. Run backup starts a real backup on the connection and reports the new backup identifier.
Incident response and helpdesk
UsersSearch by email or Okta login. Results are debounced and start at two characters. Tapping a person opens the user detail screen: profile, current status, last sign-in, and when the status last changed.
The Actions grid holds six buttons. Suspend user flips to Unsuspend user when the person is already suspended. The rest are Send password reset, Expire password, Unlock account, Clear all sessions, and Deactivate user. Each one opens a confirmation sheet that states the consequence in plain language before anything happens, for example “Signs the user out of all devices immediately” for Clear all sessions.
Deactivate is treated differently. It opens a full sheet rather than a dialog and requires you to type the target user’s email address exactly before the destructive button enables. Actions your Okta integration has not been granted show a lock badge and do not respond to taps.
Below the action grid, the same screen lists the user’s group memberships and application assignments, each with a remove control. That is the fastest path to revoking one specific access without touching the account itself.
Groups and applications
More, then Groups or ApplicationsBoth are searchable lists over the active org. Opening a group gives you an Add a member field: search for a person by email or login and add them with one tap. Opening an application gives you the same thing for assignment, plus the app’s sign-on mode and status.
Removal runs from the user detail screen described above, so the mental model stays consistent. You add from the group or app, and you remove from the person.
Containment
More, then Network zonesEvery network zone on the org with its type, usage (blocklist zones are marked), and gateway ranges. The toggle activates or deactivates the zone, which is how you enforce or lift a block on an IP range mid-incident. The screen confirms the new state after the change lands.
Recent activity
More, then System logA read-only view of the Okta system log for the active org: severity, event type, the human-readable message, the actor, and how long ago it happened. Useful for confirming that the action you just took actually registered in Okta, and for seeing what someone else changed.
Backups
BackupsA stats header (total, completed, failed, success rate) over the backup history, each row showing status, age, resource counts, size, and any error message. The Run button in the toolbar opens a sheet to start a new backup. While a backup is running, opening it shows live progress that polls until the run finishes.
Restore preview
More, then Restore previewOpens against the most recent completed backup, or from any completed backup in the Backups tab. It shows when the snapshot was taken, which org it came from, and a per-resource-type count of what a restore would cover.
The last card on the screen states plainly that execution stays on the web app, and why. If no completed backup exists yet, the screen tells you to run one first.
Executive brief
More, then Executive briefA summary sized for a leadership update: recovery score, time since the last completed backup, critical drift count over 7 days, and a status pill. It includes a share sheet that exports the summary as plain text you can paste into an incident channel or a board update, plus a suggested next action derived from whichever signal is weakest.
DR Assistant
More, then DR AssistantAsk a disaster-recovery question and get an answer grounded in indexed Okta recovery material and your own backed-up configuration, with the retrieved sources listed and a match percentage on each. The same screen carries an AI readiness score out of 100 for the active connection, broken into factors, with a recommended next step. When the knowledge base is unreachable the screen says so rather than answering silently from nothing.
Settings
More, then SettingsThe biometric requirement toggle, theme selection, the active connection with a Switch connection control, and a read-only list of every Okta scope currently granted to this connection. That scope list is the fastest way to answer “why is this button locked.”
Settings also holds Sign out and Delete account. Account deletion is permanent, requires typing DELETE and a biometric confirmation, and removes your profile and sign-in identity, all sessions across devices, stored device tokens, and account activity records within the retention window.
The Two Gates on Every Change
Both checks run on the server, before any request reaches Okta. A client that skips them does not get through, and both decisions are recorded.
Biometric confirmation
412 on missBefore submitting any change, the app calls LAContext.evaluatePolicy(.deviceOwnerAuthentication), which presents the system Face ID, Touch ID, or passcode prompt with the specific action named in it (for example “Suspend user Jane Doe”). Only when that succeeds does the client attach the header X-Biometric-Confirmed: 1 to the request.
The server checks that header first, before it parses the request body and before it loads your session context. The value must be exactly 1. Anything else, including a missing header, returns 412 Precondition Failed.
412 is the correct status here and the choice is deliberate. Your session is valid and you are authorized. What failed is a precondition on this specific request. A stolen session cookie replayed from outside the app cannot probe the mutation surface at all, because the check runs before anything else. Read requests do not carry the header and do not need it.
Okta scope capability check
403 on missEvery mutation declares the Okta scope it requires. The server reads the granted-scope list recorded on the connection when it was last capability-tested, and compares. If the scope is absent, the request fails immediately with 403 and a structured body, with no round trip to Okta:
{
"ok": false,
"error": "scope_required",
"scope": "okta.users.manage",
"remediation": "Customer admin must grant okta.users.manage to the OIN install. See /docs/okta/oin/api-service-integration#scopes."
}The app reads that body and shows you the remediation text verbatim, so the message on screen names the exact scope to grant rather than a generic permission error. Before you get that far, the same capability data disables the button and marks it with a lock badge, so an ungranted action is visibly unavailable rather than a failure you discover by trying.
If a connection has never been capability-tested, the granted-scope list is empty and every mutation is refused. Run the scope test once from the web app and the app picks it up.
Scope required per action
| Action | Okta scope | Where in the app |
|---|---|---|
| Suspend user | okta.users.manage | Users, user detail |
| Unsuspend user | okta.users.manage | Users, user detail |
| Send password reset | okta.users.manage | Users, user detail |
| Expire password | okta.users.manage | Users, user detail |
| Unlock account | okta.users.manage | Users, user detail |
| Clear all sessions | okta.users.manage | Users, user detail |
| Deactivate user | okta.users.manage | Users, user detail |
| Add user to group | okta.groups.manage | More, Groups, group detail |
| Remove user from group | okta.groups.manage | Users, user detail |
| Assign user to app | okta.apps.manage | More, Applications, app detail |
| Unassign user from app | okta.apps.manage | Users, user detail |
| Toggle network zone | okta.networkZones.manage | More, Network zones |
Read-only surfaces
Read requests do not carry the biometric header and are not fast-failed by the capability gate. Okta enforces the scope directly and the app surfaces its response.
| User search and user detail | okta.users.read |
| Groups list and a user's group memberships | okta.groups.read |
| Applications list | okta.apps.read |
| A user's application assignments | okta.users.read |
| Network zones list | okta.networkZones.read |
| System log | okta.logs.read |
Rate limits
Audit Logging and Stored Data
Every action taken from the app produces a server-side record, whether it succeeded or not.
What gets written to the audit log
- The action, the actor, and the connection it ran against
- The target: user, group, application, or network zone
- Whether it succeeded, and the Okta status code returned
- The error message on failure
- Both gate decisions, so a reviewer can see the biometric confirmation and the scope check on the same row
- Refused requests, including a scope check that blocked a mutation before it reached Okta
- Read requests: user lookup, groups, applications, network zones, and system log are logged too
Records are visible in the web app activity history alongside backup and restore events, so mobile actions and desktop actions share one timeline.
What the app stores on the device
- •Keychain: your session cookie, so you are not asked to sign in again on every launch. Removed on sign out.
- •Preferences: the active connection identifier, name, org URL, and provider; the biometric toggle; the selected theme; whether the intro has been seen; and a custom API host if you set one.
- •Nothing else. Okta users, groups, applications, backups, and log events are fetched per screen and held in memory only. No local snapshot database, no offline copy of your directory.
- •No tracking.The app does not track you across other companies’ apps and websites. Full detail on the privacy page.
Troubleshooting
The first one is by far the most common, and it is almost always a scope that was never granted.
The connection does not have the scope this action needs. The message you see is the remediation text from the server and it names the exact scope, for example okta.users.manage. Fix it in the web app: an Okta admin grants that scope to the Butterfly Security integration in the Okta admin console, then re-tests the connection so the granted list refreshes. The action unlocks in the app on the next scope refresh. If the button was locked and never fired, the cause is the same. Check Settings, Granted scopes to see the current list.
The request arrived without a valid biometric confirmation. Retry the action and complete the Face ID, Touch ID, or passcode prompt. If no prompt appears, the device has neither a biometric enrollment nor a passcode set, and the app reports “Biometric required” with the reason. Enroll Face ID or Touch ID, or set a device passcode, in iOS Settings. Turning the requirement off in the app does not bypass the server check, so leave it on.
Sign in again with a fresh emailed code. Sessions are long-lived but not permanent, and signing out on another device ends them.
You crossed the per-minute limit for that tier: 30 mutations or 60 reads. Wait a minute and retry. Rapid repeat taps on a search field are the usual cause.
That workspace enforces single sign-on, which the app cannot complete yet. It signs in with the emailed one-time code only. Pick a workspace that does not use SSO, or use the web app for that org. SSO sign-in on iOS is in development.
The workspace you signed into has no connected Okta org, or you signed into the wrong workspace. Add the org from the web dashboard first. If you belong to several workspaces, sign out and pick the right one at the workspace step.
The connection has never been capability-tested, so the server has no granted-scope list to check against and refuses all mutations. Run the scope test once from the web app, then reopen the app.
Restore previews are built from the most recent successful backup. Run one from the Backups tab or from Quick actions on Home, wait for it to complete, then reopen the preview.
Deactivation requires typing the target user's email address exactly. It is compared case-insensitively but must otherwise match in full. This is intentional friction on the one action in the app that revokes access outright.
Still stuck
Email support@butterflysecurity.org with the screen you were on and the message you saw. If it involves a security concern, write to security@butterflysecurity.org instead.
Put the on-call work in your pocket
A free download, included with every Butterfly Security plan and with the 30-day trial. Connect your Okta org on the web, then sign in on the phone and the readout and the actions are there.