Streamlining Electronic Signatures with Single Sign-on Application
Tags: webdev, 21cfr, sso, electronicsignature
Published: 2023-10-29 14:52:04

Paper trails are being replaced by electronic records, and the importance of maintaining data integrity and authenticity cannot be overstated. For industries such as pharmaceuticals, medical devices, and healthcare, adhering to regulatory standards is crucial. One such regulation is 21 CFR Part 11, which pertains to electronic records and signatures.

## Background
The project I'm working on is a web-based application that uses SAML-based SSO to authenticate users. When users want to finalize the record in the system they need to provide their username/password again to sign the record to prove they're responsible for the content of the record. 

## Challenge
The application doesn't know the username/password, we only know whether the user is logged in. So we need to ask the identity provider (idP) to verify the user identity for us. Our application should sign the record if idP says the username/password combination is valid. But only a logged-in user can sign the record, the idP won't let user input their username/password again.

We plan to follow the SSO procedure to let users sign the record (just like what we do when user log into the application), they will be redirected to the idP site and then idP will redirect them back to the application (assuming the authentication is successful). If you are familiar with web application frameworks (such as express.js, Ruby on Rails) you probably find the problem. This kind of redirect will make the application lose the record signing context. When users redirect from the idP to the application that's a new request and it doesn't tell the application which record the user wants to sign.

Our application supports SAML-based SSO, customers can integrate any identity management service that supports SAML 2.0 into the application. So the solution must adapt SAML 2.0 specification.

Let me summarise the challenges we got so far:
- How to re-authenticate users without letting them logout the application.
- How to reserve the signature context when idP tells the application that the user passes the authentication.
- Our solution must adapt to SAML 2.0 specifications.

## Solution
### Re-authenticate
The solution addresses the challenge of re-authenticating users without forcing them to log out of the application.

After reading the [SAML 2.0 specification](https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf) I've found an optional parameter we can pass in the authentication request called `ForceAuthn`.

> ForceAuthn [Optional]: A Boolean value. If "true", the identity provider MUST authenticate the presenter directly rather than
rely on a previous security context. If a value is not provided, the default is "false". However, if both
ForceAuthn and IsPassive are "true", the identity provider MUST NOT freshly authenticate the
presenter unless the constraints of IsPassive can be met.

By utilizing the `ForceAuthn` parameter in the SAML authentication request, the identity provider is instructed to directly authenticate the user, verifying their username/password combination while keeping them logged in. 

I'm using `ruby-saml`, add `ForceAuthn` to the [SAML settings](https://github.com/SAML-Toolkits/ruby-saml#the-initialization-phase).
```ruby
settings.force_authn = true
```
### Save the context
The App needs to know **WHO** sign **WHAT** record. The **WHO** is not a problem, SAML response includes all the information we need to identify who is singing the record. But how can we get the **WHAT** from the SAML response? What we need is a record ID so the application knows to sign which record. SAML is used to transfer the identified information and the record ID is obviously not a part of it.

By leveraging the [`RelayState`](https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) parameter in the SAML specification, the record ID is passed as part of the SSO request to the identity provider.
>  Sometimes a binding-specific field called RelayState is used to coordinate messages and actions of IdPs and SPs, for example, to allow an IdP (with which SSO was initiated) to indicate the URL of a desired resource when communicating with an SP.
```ruby
# initiate SSO request to idP
request    = OneLogin::RubySaml::Authrequest.new
relayState = "/record/[record_id]" 
redirect_to(request.create(saml_settings, :RelayState => relayState))
```
Under the hood, it will send a request to `your_sso_service_url?RelayState=/record/[record_id]`. The identity provider includes the `RelayState` in the SAML response, allowing the application to extract the record ID at the consume endpoint:
```ruby
# This is my consume endpoint to handle the SAML response
def consume
  response    = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => saml_settings)
  relay_state = params[:RelayState]
  record_id   = get_record_id_from_relay_state(relay_state)
end
```
You need to pay attention when using the record ID sent by the idP. `RelayState` is just a URL query string which means users can easily modify it in their browser. You must verify whether the user has the access and the authority to sign the record before you actually do the operation.

## Summary
The solution involves enabling re-authentication by setting the `ForceAuthn` parameter to `true` in the SAML authentication request. The context of signing a record is preserved by passing the record ID as the `RelayState` value in the SSO request and extracting it from the SAML response at the application's consume endpoint. Proper authorization checks should be implemented to validate the user's access and authority to sign the record. Good luck!!