This article provides an approach to implementing continuous (re)certification using Okta Workflows. It discusses the concept and then walks through the sample implementation.
Article contents:
IGA, Certification and Continuous Certification
A key focus for Identity Governance and Administration (IGA) implementations is access certification (aka recertification or attestation). The aim of this is to periodically validate the access users have and provide an audit trail of that validation to address the audit need of “prove that the access has been reviewed”. This is usually done with periodic certification campaigns that are run for a period of time, and may review all users, users in a certain application or some other risk-based categorisation.
Another approach is to use continuous certification. Rather than checking a set of accesses at a time, setup a mechanism that will detect a change in access and provide a review mechanism on that access change. This could be combined with a less-frequent periodic campaign to provide a baseline, then a continuous campaign to manage changes.
Can We Do This In Okta?
Okta, even without the new Okta Identity Governance product, has the capabilities to implement a continuous campaign mechanism.
First, Okta knows when a user is added-to or removed-from an application. It also knows when a user is added-to or removed-from a group and what applications that group is associated with. So the data model and change events provide the means to drive a campaign.
Second, Okta Workflows can trigger flows off one of those change events and implement a validation mechanism.
Implementing a Continuous Certification Campaign in Okta
So we have trigger events and Workflow flows to implement it.
Trigger Events in Okta
There are four events in Okta that could indicate a change in access: a user added to a group, a user removed from a group, a user assigned to an application, and a user unassigned from an application.
These events are available in the Okta Connector in Okta Workflows. For details of the events, see https://help.okta.com/wf/en-us/Content/Topics/Workflows/connector-reference/okta/okta.htm.
Workflows Flows
There are ten flows used in this implementation (detailed below):
- A00 – Suspend User – flow triggered off an API hook to suspend a user
- F00/01/02/03 – Main flows off the four events listed above to process the access changes
- S00/01/02/03 – Subflows to support the main flows
- U02 – Export Audit Table to CSV – utility flow
The main flows will run in response to an event in Okta and send an email to the users manager. The manager will review the email and is offered the option to suspend the user if they access change is suspect. (note, suspending the user is a safer option that removing them from the app or group as there may be group rules that have dynamically added them in the first place). It will also send a Slack message and write an audit event into a Workflows table.
A sample email is shown below:

This email has been generated because the user was added to a group (Sales) which means they get access to Salesforce. For completeness, the flows will also show the apps the user has (including the new one(s)) to identify any possible conflicts or inconsistencies.
As mentioned above, the manager is given the option to suspend the user with a link. The link was kept in that form for demonstration purposes (show the API endpoint called) but you would obfuscate it or tokenise it.
We will look at the flows contributing to this.
Main Flows (F**)
There are four main flows, one for each event.

They all follow the same structure, so we will look at the first – F00 – User Added to Group. This flow will:
- Trigger off the user added to group event
- Lookup both the user and group to get additional details
- Check the group is NOT the everyone group (it will stop the flow if it is)
- Get the application list for the group and the user
- Build the API endpoint (suspend) URL and get the manager email for the user
- Format and send an email
- Format and send a Slack message
- Write and audit event
Let’s look at each section of this flow.
The flow is triggered by a User Added to Group event and the event will supply both the Okta user and group.

A Read User card will get details of the user, including a custom field of Manager email. There is a Read Group card to get more information about the group.

We don’t want to process events against the Everyone group, so there is a Continue If card to check if the group is not equal to “Everyone”.

The next four cards use sub flows to get additional data via Call Flow cards:
- The S00 – Get App List for Group flow will return a list of applications that the group is assigned to (in both a text string and a formatted HTML list)
- The S03 – Get App List for User flow will return a list of applications already assigned to the user (in both a text string and a formatted HRML list)
- The S02 – Get Flow Variable flow is passed an environment variable name and returns the value of the environment variable (which are stored in a Workflows table). In this case it’s returning the email of the system administrator and the URI of the suspend flow API endpoint.
The values returned from these calls are used in later cards.

The first means of communication is an email to the manager. The two Compose cards shown above will build the email subject and HTML-formatted body. These are passed to the Google Send Email card for emailing out.

The second means of communication is to send a message to a Slack channel (note that it doesn’t send it to a specific user such as the manager, but it could if it had the SlackID of that user).
It uses a Compose card to build the Slack message. Then it uses the S02 – Get Flow Variable sub flow mentioned above to get the SlackChannel environment variable. The outputs from these are then used in a Slack Send Message to Channel card.

As this is a governance mechanism being implemented, it makes sense to produce an audit trail of the changes and result. A Compose card is used to write the audit message. Then a sub flow, S01 – Write Audit Event, is used to write this event to the audit trail (table).
The other three main flows are similar.
API Endpoint Flow (A00)
The A00 – Suspend User flow is triggered by the manager clicking the link in the email.

The link was built from the API Endpoint card that starts the flow (and stored in the environment variables table mentioned above).

When the flow is triggered, it is passed the userid via the URL query string. Within a Try/On Error card, it will attempt to suspend the user with an Okta Suspend User card.
Whether successful or not, the rest of the flow is basically the same as the earlier flows:
- Read the user to get user details for use in emails, slack and the audit message
- Format and send an email
- Format and send a Slack message
- Format and write an audit trail event
That concludes the discussion of the main flows. The next sections look at the called flows (now called Helper flows in Workflows terminology).
Sub Flows (S**)
I have a number of common flows, like shared subroutines, called from the main flows. I’ve called them “Sub flows” but in Workflows terminology they are just helper flows. They are:

All of the flows and their purposes have been mentioned above. I will walk through some of them to highlight some Workflows capabilities.
The first flow, S00 – Get App List for Group will retrieve the list of applications assigned to a group. The flow is passed the groupId for the group the user has just been added to (or removed from).

There is (currently) no Okta card to get the apps for a group, so a Custom API Action card is used with a Compose card used to build the API URL. The Custom API Action card, if successful, will return the results in the HTML body object.

The List Pluck card is used to pull the label attribute out of the Body. This is the app name list. The second card, List List to Text, will format that list into a text string (in this case only a space is the separator). The third card, List List to HTML (it’s actually a List to Text card), will format the list into a string with a <li> separator.

A Compose card is used to build the HTML string for an unordered list of app names. Then the Return card will return both the string and HMTL formatted lists to the calling flow.
The S01 – Write Audit Event flow is passed the affected user, who the event was performed by, and the message shown above. It determines the current date/time and the timezone (environment variable) and formats the time. These values are written to the Audit table.
The S02 – Get Flow Variable flow is passed an environment variable name. It performs a lookup against the environment variable table and returns the corresponding value. An example of the table is shown below.

The S03 – Get App List for User flow is almost exactly the same as the S00 flow. The only difference is the API URL used (/api/v1/users/$userid/appLinks
).
Utility Flows (U**)
The last set of flows are the utility flows. In Workflows terminology they are also Helper flows. The distinction is that these flows don’t support the main flows above, but rather are standalone utilities.

The only one of interest here is U02 – Export Audit Table to CSV. It is a simple flow to dump all the rows in the Audit table to a CSV file. It is run on demand but could be scheduled.

It uses a single Tables Export to CSV card. The flow could easily be expanded to store the CSV file on a google drive, and send an email to someone with the link to the file.

Design Points
The above article has shown how the Okta events and Okta Workflows can be used to implement a continuous campaign mechanism. The implementation of the flows in Workflows also highlights some design considerations.
- Use of sub flows (sub routines) – the structure of Workflows means helper flows will be necessary for any repetitive task. It makes sense to structure helper flows so that they implement common functions and can be used across multiple main flows. It may mean passing more data so the sub flow can be leveraged from multiple main flows, but it makes the implementation more efficient and easier to understand.
- Use of environment variables in a table – this implementation needs to use some common “environment variables” across the different flows. Rather than hardcoding them in each flow, it’s more efficient to store them in a single place and access them via a common helper flow. A workflows table makes sense, but it could be an external datastore accessed via a lambda function, a file stored on a drive or other ways Workflows can get to data.
- Use of a table for the Audit trail – this is a governance implementation where an auditor would like to see evidence of what has occurred, such as user access changes. It was simple to implement this by using a common sub flow to write audit records to a Workflows table (and having a utility flow to export it).
- Naming standards – Okta Workflows does not enforce naming standards on flows or tables. But it makes sense to give them meaningful names. I find it useful to prefix the flows (and tables) by unique identifiers to indicate the type of flow and to order them alphabetically.
Hopefully these points will help when you’re designing and implementing processes in Workflows.
This concludes this article.
Sample Code
The workflows example described above can be downloaded from https://github.com/iamse-blog/workflows-templates/tree/main/dae-iga03-ContinuousCertification