This article looks at a new approach you could use to perform Separation of Duties (SoD) checking from Okta Access Requests using Okta Workflows. It shows two approaches you could take to get SoD analysis into the request a soon as it’s raised so that the reviewer has the information at hand before approving the request.

Note that this integration leverages the Okta Identity Governance API that is still in beta but available to all OIG customers.
Introduction
Some time ago I wrote an article here on running SoD on group assignment. It was built around the concept of Okta groups representing Roles and Separation of Duties (SoD) rules based on toxic combinations of groups. The limitation of this approach was that it was after the point of group assignment and thus after the reviewer had approved the request.
Two recent additions to Okta Access Requests, the ability to call Delegated Workflows from within a Request Type and the ability to update an open Request via API, has meant that SoD checking can be done as a Request is raised and before the reviewer reviews, meaning that can head-off any SoD conflicts. As this information is in the request body, it is also stored away and can be reported on later.
This article presents two approaches for performing a SoD check and returning the results to the request body:
- When a new request is raised, an event is written to the Okta system log with details of the request. A workflow is listening for that event, performs the SoD checking, then returns the results to the request via the requests API.
- Within the request type, the first action is to call a workflow (before the first approver step). This workflow is similar to the first – it will perform the SoD check then return the results to the request via the requests API.
Overview of the Two Approaches
Both approaches use the same mechanism to perform the SoD check, but are triggered in different ways and are passed different sets of data so need to do some different initial processing before calling the shared code.
Performing SoD Check Based on System Log Event

This approach relies on the new request creating an event in the Okta System Log (access.request.created
) to trigger the workflow (see https://iamse.blog/2023/07/20/oig-access-requests-posting-additional-information-into-a-request/). This article also includes details of the new API call.
The technical details are further down in this article, but the workflow will:
- Extract the details of the event and check to see if it’s one of the ones we want to do a SoD check on
- Call a subflow to determine what this access request is granting (based on a static table), i.e. which groups and/or applications will be granted with the request
- Call a subflow (Map) to check the SoD policy by looking for SoD rules (static table) that contain the entitlement(s) being requested, then getting all entitlements for that user to see if any of them (plus the new one) trigger a SoD violation
- For any violations found, a message body is built (there could be multiple violations)
- Then the workflow sets up for the API call and writes that violation back into the request
This approach is built around the assumption that a Request Type will add a user to one or more groups/applications. As there’s currently no programmatic way to extract the relationship between a Request Type and the assignments it performs, a static table is maintained tying them together. This is not the ideal approach, but we’re limited by the OIG API at this stage. This approach will be most effective for simple Request Types that are granting a entitlements with no variable logic (e.g user will always get X, instead if determining what user gets based on selection or logic).
Once a request is created (via the webUI, Slack or Teams integration, or even via APIs) it will write the event to the Okta System Log and trigger the workflow. This is an asynchronous process, but should be quick enough to post the SoD information into the message before the reviewer goes to review the request.

The reviewer has enough information to help them make the decision to approve or deny the request.
Performing SoD Check When Called from Within a Request

In this approach, the Workflow to trigger the SoD check is called from within the Request Type itself using the new Workflow action (see https://iamse.blog/2023/07/17/oig-access-requests-calling-an-okta-wokflow-from-within-a-request-type/).
The method of calling means that the workflow has access to different data to what is passed in the System Log Event in the previous approach. In this case you can only pass data that is being used in the request, like the requesters email, the request type name and the access being requested.
The called workflow will:
- Consume the data sent over from the request
- Set up data for the SoD check: find out if the entitlement is a group or application and setup the name; find the userid from their email and find the requestId for this request
- Call a subflow (Map) to check the SoD policy by looking for SoD rules (static table) that contain the entitlement(s) being requested, then getting all entitlements for that user to see if any of them (plus the new one) trigger a SoD violation
- For any violations found, a message body is built (there could be multiple violations)
- Then the workflow sets up for the API call and writes that violation back into the request
The last three steps are the same as in the earlier approach – a common subflow is being called with the userId and newEntitlement.
It should be noted that currently there is no way to pass the requestId to the workflow (the earlier approach has it in the Event details). So I had to build a workaround that will get all open requests in the last x minutes, find all that match this user and request name, then select the newest.
This approach is better suited to specific Request Types where the requester is selecting which access they want and this can be passed to the Workflow.
As shown in the diagram above, once the questions have been answered in the Request Type, the Workflow action is called to run the SoD check. This action will write a message to the request saying the workflow has been initiated, then when the workflow runs the request will be updated with any SoD violations.

The reviewer has enough information to help them make the decision to approve or deny the request.
Technical Details
Let’s look at some of the technical details of the integration. You should refer to the other two blog articles linked above for details of the two new functions used here.
Note that there are common tables and flows used across this set (such as storing keys in a environment variables table and flows to access and use them) but they are not detailed here.
Common SoD Check Workflows
Both approaches use a common set of subflows to perform the SoD check and return any violation information to the calling flows. It consists of some Workflows tables and a series of flows.
Workflows Tables
There is a table for the SoD Policies.

The table has a row for each SoD Rule. The columns are:
- id – a unique id for the rule (not currently used)
- name – a name for the rule, which appears in the output for any violation
- description – a description for the rule, which appears in the output for any violation
- scope – maximum number of entitlements a user can have before a violation is flagged (e.g. if it’s “1” you can have one of the entitlements, but once you add a second it flags a violation)
- entitlements – a comma-separated list of entitlements (groups and/or applications). They are of the format prefix:name (e.g. A:O365 Guest is the “O365 Guest” application).
- severity – the severity of any violation, which appears in the output for any violation
This table is manually maintained.
SoD Check Workflows
There is a main subflow and a series of helper flows called to implement the SoD check. It will:
- Get the relevant SoD policies for this new entitlement
- Get the users current entitlements,
- Check to see if any of the matching SoD policies are violated based on what the user already has, and
- For any violated SoD policies, format the output and return it as text to the calling flow
This flow is called by a Last Map card so will take each list item (i.e. new entitlement being added) and return a violation message (or blank if there is none).
Let’s look at the flow in more detail.

- It is passed the Okta userId and newEntitlement (in Prefix:Name format as per the table above)
- It calls a helper flow (S10) to find all SoD rules that contain the new entitlement. The helper flow reads the SoD Policy table and returns a list of rules (as objects). This reduces later work – if a rule doesn’t contain the new entitlement being added, there is no point in further checks against it.
- It calls a helper flow (S20) to find all the current entitlements for the user. The helper flow uses two Okta cards – one to get all groups for the user and one to get all applications for the user. It returns three lists: a consolidated list of entitlements in the Prefix:Name format as used in the SoD Policies table, a simple list of all group names and a simple list of application names. The latter two are used in formatting the output to go back to the response.
- It calls a helper flow (S30) to check if each matching SoD rule is violated. It is passed the new entitlement, user entitlement list, and SoD rule object. It works out how many entitlements the user already has in the rule and whether adding the new one would be more than the scope (number allowed). It builds a new object with the SoD rule object but also a Violation flag (true/false). So the outcome of running this in a List Map card is that the list of matching SoD rules will be updated to indicate whether each has a violation or not.

- The updated SoD rule list is filtered for only those flagged as violated (i.e. violation = true from the S30 helper flow)
- This (hopefully) reduced list is processed in another List Map card using a helper flow (S40) that takes the SoD rule object and builds up a text representation to go back to the request. This text message includes newline and simple markdown characters.
- As there may be multiple violations for this new entitlement, they are consolidated into a single bit of text (with newlines)
- This consolidated text is returned to the calling flow
Thus the set of flows are passed the user and new entitlement, find out the SoD rules with the new entitlement, determine what entitlements the user already has, then checks for violations with the current+new entitlements and for each violation found formats a text message to go back to the calling flow.
Next we will look at the workflows that are calling this SoD Check subflow.
Workflow Triggered by System Log Event
The first implementation we will look at is where the SoD Check has been driven by the access.request.create
event sent to the Okta System Log.
Workflows Tables
This solution requires an additional table that maps the Request Types to Entitlements.

This table is manually maintained.
Event-Driven Workflows
The workflow (E00) is triggered by an access.request.created
event in the Okta System log. At a high level the flow will:
- Extract the event data and check if this request is one we want to run a SoD check against (all new requests will create that event, but we might not want to run SoD checks against all of the them).
- Lookup the table above to find the entitlement(s) for this request
- Call the SoD Check subflow for the list of entitlements and get back any violation messages
- Use the new Requests API to add the message to the request
Let explore in a bit more detail. The first card in the flow is the Okta Access Request Created event card. It contains all information about the event, but specific request details are inthe Debug Data object. The requesterID (userid of the Okta user requesting access), accessRequestId (request id) and accessRequestSubject (request type name) are pulled from the event.
We have a hardcoded list of Request Types to run SoD checks on, so if the name isn’t in the list it’s not allowed in (“not in those shoes mate”).

It calls a helper flow (E01) to get the entitlements from the table above for this Request Type (by name). A list of entitlements, formatted in the the Prefix:Name format, are returned.

The Sod Check subflow described above is called for that set of entitlements passing in the entitlement and userid. This is run as a List Map card, so the result is a list of violation messages for each entitlement (the message will be blank if there are no violations).

Finally the new API use used to write the message onto the request (see https://iamse.blog/2023/07/20/oig-access-requests-posting-additional-information-into-a-request/).

That completes the exploration of the flow for the first approach. The flow for the second approach is similar.
Delegated Workflow Called from Within a Request Type
The other workflow using the SoD Check subflows is the Delegated Workflow called from within the Request Type. It uses no additional tables.
Delegated Workflow
The Request Type is configured to call a specific workflow (D00). In short this flow will:
- Find the entitlement (group or application) and user passed in
- Find the request id
- Call the Sod Check subflow to get any violation messages
- Use the new API to write the message back into the request.
The first part uses two helper flows (D01 and D02) to find and format the entitlement name and store it in a list (for the SoD check subflow) and get the userid for the user based on email.

The next part finds the request id in a helper flow (D09).

This helper flow (D09) is a workaroud as there’s no way to pass the request id from the Request Type to the Workflow. It will:
- Build the API Authorization header and URL
- Build a query to set the max requests to 200 and to filter Use the Requests API call to search for all requests lastUpdated lass than 30 minutes ago (see screen shot below)

- Run the API call with query and strip out the list of results
- For each found request, it checks (via helper flow) whether it contains the same userid and request name as the request that triggered this
- For each match (and there could be multiple in the time window) is sorts them and takes the last (latest) one, returning it’s id
Back to the main flow and it calls the SoD check subflow as described earlier, resulting in a list of violations formatted as messages. These are consolidated (which will be a list of one) into a text message.

The last section is the standard set of cards to setup and call the API to write the message into the request. This is covered in detail in the other article.

This concludes the technical exploration of the flows.
Conclusion
New capabilities within Okta Access Requests, to add messages to a request and to call a Delegated Workflow from within a Request Type, have opened up the ability to run Separation of Duties (SoD) checks after the user has submitted an access request and before the reviewer (such as their manager) has had a chance to review. This is much more timely than earlier examples that leveraged changes in group membership after a request has been approved.
This article has shown two examples of integrations to run a SoD Check, either via the Access Request created event written to the System Log or called directly from a Request Type, that perform a SoD violation check and return any violations (with additional information) to the request, so the reviewer can review. It has explored the Workflows-based implementation of the examples so you could build them yourself.