Guides · Okta Workflows · Lifecycle

Suspend inactive Okta users, without suspending active ones

A 90-day inactive-user clean-up is one of the first flows most Okta teams build. It is also easy to get wrong in ways that only show up when someone who signed in yesterday can’t sign in today. This is the version that is safe to switch on.

Assay · 26 October 2026 · about 8 minutes

The short version.

  • Check that Last Login sees how your people sign in before you trust it. Sign-ins that start at the app may not update it.
  • The Okta search can’t filter on Last Login, so stream your active users to a helper flow and compare dates there.
  • Write candidates to a table first. Act on them in a second flow, after a person has looked.
  • Suspend, don’t deactivate. Keep an exclusions list, a cap, and a log of every change.

1. What Last Login does not see

Every Okta user has a Last Login date, and the obvious flow suspends anyone whose date is more than 90 days old. The catch is what updates it.

Okta’s own support article on inactive users explains it. A sign-in through the Okta Dashboard updates Last Login, and so does an IdP-initiated sign-in. A person who goes straight to an app (an SP-initiated sign-in) may not update it. Someone who opens Salesforce from a bookmark every morning can look inactive for months.

Test it in your own org before you build anything. Pick five people you know used their apps this week, open each one in the Admin Console, and compare their Last Login with what you know. Then choose:

The dates look right. Most of your people start at the Okta Dashboard. Use Last Login as it is.
Some dates are old for people who are clearly active. Record each person’s last sign-in yourself. Okta’s article describes a daily flow that reads the System Log for user.session.start and user.authentication.sso events and writes the time to a custom profile attribute (for example lastSignIn). The clean-up then reads that attribute instead of Last Login.

The System Log keeps 90 days of events, so start recording well before the first clean-up. A person who has no recorded sign-in yet is “unknown”, not “inactive”.

The natural next step is a search such as lastLogin lt "2026-07-28T00:00:00.000Z". It fails with “Invalid search criteria”.

Okta’s user query reference lists the top-level properties the search accepts: id, status, created, activated, statusChanged and lastUpdated, plus profile attributes. Last Login is not one of them.

So search for what you can, which is active users, and compare dates one user at a time in a helper flow. Okta’s support article on custom search criteria in Workflows makes the same point.

3. The flows, card by card

Two flows and a helper. The first finds candidates and changes nothing. The second acts on the ones a person has approved. Splitting them is what makes the clean-up safe to schedule.

Flow A: Inactive users — find (weekly, changes nothing)

  1. 1Scheduled Flow (event). Once a week, for example Monday at 06:00 in your own time zone.
  2. 2Date & Time — Now. The time this run started.
  3. 3Okta — List Users with Search.
    • Custom Search Criteria: status eq "ACTIVE".
    • Result Set: Stream Matching Records, choosing the helper flow below.
    • Pass now and days (90) in the streaming state.
    Streaming sends each user to the helper flow one at a time, so the flow does not stop at 200 users.

Helper: Inactive users — check one

  1. 1Helper Flow (event). Two Object inputs named exactly Record and State.
  2. 2Object — Get Multiple from Record: id, profile.login, lastLogin (or your profile.lastSignIn), created.
  3. 3Tables — Search Rows in an Inactive — exclusions table, by login. Then Branching — Continue If no row was found. Add your break-glass admins, service accounts and API-only users to the table before the first run.
  4. 4Branching — Assign If. If there is no sign-in date, use created instead, and mark the reason “never signed in”. Someone created last week has not had a chance to sign in yet.
  5. 5Date & Time — Difference between that date and State’s now, in days.
  6. 6Branching — Continue If the difference is at least State’s days.
  7. 7Tables — Create Row in Inactive — candidates. Columns: login, user id, last sign-in, days, reason, found at, and an Approved column (True/False) left false.

Flow B: Inactive users — act (after review)

  1. 1Scheduled Flow, a few days after Flow A, or run by hand once someone has reviewed the table.
  2. 2Tables — Search Rows in Inactive — candidates where Approved is true and Status is empty.
  3. 3Branching — Continue If the count is below your cap (see section 5).
  4. 4List — For Each row, calling a small helper that does the following:
    • Okta — Suspend User, inside Error Handling — If Error.
    • Tables — Update Row: Status “Suspended” with the time, or “Failed” with the error message.
  5. 5Send a summary to the team: how many were suspended, how many failed, and a link to the table.

Approval can be as light as someone ticking Approved in the table. It can also be an email to each person’s manager with a link to a Workflows form. Either way, a person has looked before anything changes.

4. Suspend first, deactivate later

Use Suspend User for the first step:

  • a suspended person can’t sign in;
  • their groups and app assignments stay as they are;
  • Unsuspend User puts them back exactly as they were.

Deactivating is a different act. Depending on how each app’s provisioning is set up, it can deprovision the person’s accounts in those apps, and the way back is slower. Deactivate in a later step, for example 30 days after suspension with no complaint, if your policy calls for it.

5. Before you switch it on

  • Run Flow A alone for two weeks. Read the candidates table and look for anyone you know is active. If you find one, go back to section 1.
  • Fill the exclusions table first: break-glass admins, service accounts, integration and API users, and anyone on long leave whom HR has asked you to keep.
  • Set a cap. If Flow B finds more approved rows than you would ever expect (say 50), it stops and tells you instead of acting. A wrong date format or a broken attribute then fails safe instead of suspending half the company.
  • Handle errors on every card that changes something, and write the result to the table. A suspend that failed quietly looks the same as one that worked.
  • Give the flows an owner and a description. Six months from now, someone will ask why a person was suspended. The table answers the question; the description says where to look.
  • Tell people. A short note to managers before the first real run saves a lot of help-desk tickets.

6. Okta’s own starting points

Okta Workflows has a template called Identify Inactive Okta Users, and Okta’s inactive-user article suggests starting from it. If you use it, apply sections 1, 4 and 5 to it: check what Last Login sees, suspend rather than deactivate, and add exclusions, a cap and a log.

7. Build it with Assay

Assay Studio builds Okta Workflows from a plain-English request. You could describe this guide in one paragraph:

Describe it

“Every Monday, find active users with no sign-in for 90 days, skip the people in an exclusions table, and write the rest to a candidates table. A second flow suspends the approved ones, stops if there are more than 50, records every result, and emails the team a summary.”

Assay designs the flows and shows each one and the price before anything is built. Designing and pricing are free and need nothing installed. What it builds comes with error handling on the cards that change things, and passes Assay’s published control catalogue. You import the file and test it in your own tenant, as you would any flow.

Already have an inactive-user flow? An Assay assessment of your export shows where it lacks error handling, logging or a clear owner, and which cards to look at.

Sources

Okta and Okta Workflows are trademarks of Okta, Inc. Assay is not affiliated with Okta. Card names are as they appear in Okta Workflows at the time of writing; check them against your tenant.

Skip the card-by-card. Describe it.

See every flow and the price before anything is built.