Workflow Approval Via Encrypted Email Links

A common workflow process is often the access request scenario – a user requires access to something, and that something requires an approval before the provisioning can be completed. Typically this is done via notifications, a dashboard and perhaps an email notifying the approver that they have a task that requires their attention.

However, what if the approver doesn’t want to, or cannot, access their dashboard to approve the request?  An alternative is to embed workflow approval questions into an email, with fully self-contained links that contain encrypted payloads to approve or reject the request. (NB a further extension to this is to be able to respond to workflow requests directly via email / SMTP).

A way to do this is to simply send an email during the workflow instantiation that contains links to approve or reject the request.  But how can those links be securely created to avoid tampering, replay and misuse?  There a few neat steps in the ForgeRock platform that can simply come to the rescue. (NB this assumes that the email traffic / account is secure, which might not be the case…!).

My use case will look like something like this:

  1. Helpdesk operator requests access to impersonate an end user for a set period of time – say 5 minutes
  2. The end user will receive an email notification with two links – Approve or Reject
  3. Each link will go to two specific OpenIDM custom endpoints – ../endpoint/approveImpersonation or ../endpoint/rejectImpersonation
  4. Each endpoint will take a ?payload= argument that will contain an encrypted value
  5. That encrypted value will be contain the end user’s Id and a unique reference to their workflow task instance
  6. As every request into OpenIDM needs to be authenticated, we’ll route the request via OpenIG to add in an authentication header
  7. The custom endpoints will verify the payload, decrypt, find the appropriate workflow task instance and complete the workflow request task
  8. If approved…the workflow will provision the end users Id into the helpdesk operators account under an attribute called impersonationId
  9. The workflow will then suspend and return n-minutes later, based on the time selected in step 1 and deprovision the attribute in step 8.
The architecture at a high level looks something like this:

The main element of this is the workflow.  This is a simple access request style workflow with two interesting components. First is the sending of an email with the two links – both of which are encrypted using the openidm.encrypt function. The second is a time boundary that removes any changes the workflow makes after a selected time window.  The encrypted email payload contains a unique reference that is attached to the task instance.  The unique reference is created using the openidm.hash function, that takes the requester Id, requestee Id and the current time in ms.

To trigger the workflow, the enduser and a time element are entered.

This triggers the sending of the notification email with the two links.

The end user simply selects the appropriate link.  The link automatically redirects via IG to snowball the appropriate authentication headers and completes against the OpenIDM endpoint.

The endpoint verifies the payload argument exists, that the encrypted value is in tact, decrypts, finds the appropriate workflow task, compares the two hashed verification codes and completes the appropriate approve or reject action.

The workflow then contains an intermediate timer event that is used to act as a stop watch – to basically reverse any changes that are made, acting like a temporal condition.

<intermediateCatchEvent id=”timer”>

The length variable is taken from the submitted workflow form.  After the timeDuration has completed a simple patch removes any values provisioned to the user.

<scriptTask name=”Cleanup User” id=”cleanupRequestingUser” >

queryParams = [“_queryFilter”: ‘/userName eq “‘+startUserId+'”‘]
userToPatch = openidm.query(“managed/user”, queryParams)

patchParams = [[operation:’replace’, field: ‘idToImpersonate’, value : “”]]
openidm.patch(‘managed/user/’+userToPatch.result[0]._id, null, patchParams)

The code for the above sample is available here.

This blog post was first published @, included here with permission from the author.