Blog

HomeResourcesSignicat Authentication and Document Signing in Ruby on Rails
significant-doc-signingsignificant-doc-signing

Signicat Authentication and Document Signing in Ruby on Rails

Signicat is an e-id authentication service provider that operates in Europe and extends that functionality further by providing users the ability to electronically sign and store documents. They provide authentications for several others e-id’s as well, the complete list can be seen here.

They have plentiful resources and documents on how to integrate their service into your website however I found it really difficult to make the ends meet with the help of those documents, as most of them are good as individual problem solvers but don’t help much in the bigger picture.

I will give several references and links to their documentation here but the problems I faced in finding the links and connecting the dots will be ironed out in this document.

Furthermore their code is in C# and/or Java which is translated to ruby.

When everything fits together it works great and looks easy, but putting it together is the hard part. So buckle up.

 

Starting Off

We will be authenticating a user through Signicat. This exercise will also help in listing down what is needed and in knowing how to get a user for this testing and how these processes work.

 

Creating a Test User

We can create a fully working application but to know if its truly working we will need some test users for credentials authentication. For this document I will use Nem-id’s. The information stored in the response received from Signicat depends on which e-id is used for authentication.

The link mentioned below explains in detail how to get the users in working order:

https://support.signicat.com/display/SUP/NemID,+creating+test+users

However just skip the first step, and instead of filling the form in second step, use ‘Autofill’ button.

Once the user is good to go, we need to authenticate him through the service (this was the point of creating him in the first place to test our application with his credentials).

‘https://env.signicat.com/std/method/service?id=method:profile:language&target=target

Quick brief on what is what in this url. The blue part is all generic and the colored ‘RED’ part is explained in different paragraph for each ‘RED’ placeholders.

Env: is just telling signicat what environment you are currently running. Test is for development and test phase for production it is replaced with ‘id’

Service: name of your service, for testing ‘Shared’ is used once you get the service name for which you have paid for it should be replaced with the service name, as it is configured separately for each service and might need to be adjusted. Code that worked on shared doesn’t necessarily will work on production (trust me it happened and after emailing with Signicat the service was adjusted to our needs, PHEW!!!).

Method: it specifies which e-id provider credentials the user will provide, for the scope of this article I will use ‘NEMID’.

Profile: is the name of the graphical profile, you would like to use. If you don’t have a graphical profile, you can omit the value and the default profile will be used. I in my case used default, that doesn’t interfere in any way and is a good place holder for future in case one does use a profile.

Language: the language that the interface will be shown in. Its better to use a language one is comfortable with and once everything is finalized then change the language as required.

Target: It’s the URL on which the response from Sigincat would be received. This method would be an encoded URI.

More can be read on this here.

 

What to do With the Response Received

What received is SAML 1.1 response (if authentication is successful), and it will be received on the target url described above, so it will have a proper route defined. In my case I have a controller whose action receives the response. Now how that response is handled is a different story, because it does need to be translated into a readable format because SAML can’t be used directly.

I used Omniauth-SAML gem for this purpose, all was good and hay, the response was being translated but the problem occurred when the SAML was being validated. All the gems I came across were build for SAML 2, and Omniauth-SAML will throw an error in validating the translated response, so I had to hack the gem or should I say remove the ‘validate’ part (wink, wink).

My hack involved overriding the ‘callback_phase’ function in the GEM and commenting out the validate part, I am commenting it out here and not entirely removing it because it was later included with change in validation policy, I won’t go into that because it is out of scope of this article, and right now we should be focusing on running the application so we can concentrate on how the data will be used in our application I did was to store the information in the Database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
require 'omniauth/strategies/saml'
module OmniAuth
module Strategies
class SAML
def callback_phase
unless request.params['SAMLResponse']
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing")
end
response = Onelogin::Saml::Response.new(request.params['SAMLResponse'], options)
response.settings = Onelogin::Saml::Settings.new(options)
node = REXML::XPath.first(response.document,
"/Response[@ResponseID='#{response.document.
signed_element_id}']/Assertion/AttributeStatement/Subject/NameIdentifier[@Format='#{
options[:name_identifier_format]}']")
@name_id = node.text#response.nem_id
@attributes = attributes_patch(response)
if @name_id.nil? || @name_id.empty?
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing 'name_id'")
end
# response.validate!
super
rescue OmniAuth::Strategies::SAML::ValidationError
fail!(:invalid_ticket, $!)
rescue Onelogin::Saml::ValidationError
fail!(:invalid_ticket, $!)
end
end
end
end

We just required the file that we will be overriding the functions in, and just commented out the response.validate! line.

Instead of hitting the url directly I used ‘Omniauth’ gem because it is well integrated with

‘OmniauthSAML’.

1
2
3
4
5
6
7
8
Rails.application.config.middleware.use OmniAuth::Builder do
provider :saml,
:issuer => issuer_name,
:idp_sso_target_url => target_url,
:idp_cert_fingerprint => idp_cert_fingerprint,
:name_identifier_format => name_identifier_format
end
require 'saml_patch'

This code goes in config/initializers/omniauth.rb. The code is explained below.

Issuer: takes the registered name of your service, it is an Omniauth required field as some of the authentication services require the issuer name to be present, in Signicat’s case they don’t have any such compulsion, so the value can be anything, just to surpass Omniauth field requirement validation.

Idp_sso_target_url: the Signicat’s structured url will be placed here.

Idp_cert_fingerprint: The SHA1 fingerprint of the certificate. This is provided from the identity provider when setting up the relationship.

Name_identifier_format: This will tell omniauth what kind of packaging method would be used. Again this is required by omniauth because Signicat sends the  package depending on methods specified in the url. So in NEM-ID’s case the value is “urn:kantega:ksi:3.0:nameid-format:fnr”.

And in the end we have required the patch we created to override ‘callback_phase’ method.

Now when we want to hit the target we will assign this code to any variable and it will hold the response from Signicat.

1
2
foo = request.env["omniauth.auth"].extra.raw_info
bar = foo.to_hash

The response will be be available in a hash in ‘bar’ variable and valid (for now) for use.

Now as the framework is more or less created we will move onto signing the document.

For signing we need to make SOAP calls to Signicat’s document service url.

The url to which the call’s are being made for this purpose is:

https://test.signicat.com/ws/documentservice-v2?wsdl

It is recommended that you visit their site or contact their support to know which service is being lately used, and adjust your SOAP call accordingly.

Now to make the SOAP call I used a nifty little gem called SAVON. The methods created is given below followed by explanation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def send_signature_request(user, task_id, completion_callback_url)
document_id = id
ref_sds_id = upload_file
client = Savon.client(wsdl: 'https://test.signicat.com/ws/documentservice-v2?wsdl', raise_errors: false)
response = client.call(:create_request,
message: {
service: 'shared',
password: 'password',
request: {
profile: 'default',
language: 'en',
task: [{
:@id => task_id,
:@bundle => false,
subject: {
:@id => user.id,
"national-id" => user.cpr,
},
:attributes! => {"document-action" => {type: "sign",
"order-index" => 1, "send-result-to-archive" => false, optional: false}},
"document-action" => {
:attributes! => {
document: {
id: document_id, "ref-sds-id" => ref_sds_id,
"send-to-archive" => false,
'xsi:type'=> 'tns:sds-document'
}
},
document: {
description: 'description of file goes here'
}
},
signature: {
method: ["nemid-sign"]
},
"on-task-complete" => completion_callback_url
}],
}
})
response.success? && response.body[:create_request_response]
end

So the way it is structured is that we created a function which takes a user (which is called subject in Signicat’s terminology), task_id, every task requires a unique id, by which its status can be requested and identified on our end. I am sending this to our function you can create it inside the function as well, its a choice of personal preference or requirement of the scenario.

Completion_callback_url, this is the url which will accept the response from Signicat on completion of task, similarly there are can be 2 more url’s one for postponing the task and other for on task cancel. However here we will just cater the completion of task for the sake of simplicity and more or less it will act in the same way to this.

This url is made by appending the controller’s action route defined to the base url of your application.

Since I had created this function inside my model which stored the files(pdf’s) for signing, so the id

is being assigned to document_id is readily available as model’s attribute. Since there are more than one way of providing the file to Signicat which can be read about here.

I went with sds-document which is session data storage provided by Signicat. And in my function called another model function created called upload_file

1
2
3
4
5
6
7
8
9
10
11
def upload_file
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
file = File.open("file_path/file.name", 'rb')
request = Net::HTTP::Post.new(url.path, {"Content-Type" => file.content_type,
"Content-Disposition" => "form-data; name=\"datafile\"; filename=\"file.name\"\r\n"} )
request.basic_auth('shared', 'password')
response = http.request(request, file.read)
response.body
end

In this call we are just uploading the datafile to the shared session data storage provided by Signicat. And it sends back the ref_sds_id.

Now that file is uploaded and we have everything in order lets break the code in chunks and explain each chunk. So savon client call won’t be explained as plentiful of documentation is available, and what I found boggling in in implementation was the actual message that we send to the wsdl call which we post to :create_request.

In ‘message’ before we can define any kind of activity, we need to provide our credentials.

1
2
3
message: {
service: 'shared',
password: 'password',

With these credentials if we are authenticated, then we move further to the request part which holds all of the settings and the task.

request: { profile: ‘default’, language: ‘en’,

Here we tell the service which profile to use(if created), in my case I just used their default profile, and the preferred language in which the prompts will be displayed to the user. Then comes the most important part which is the defining the task in which the actual signing will take place.

1
2
3
task: [{
:@id => task_id,
:@bundle => false,

As mentioned previously @id is given the value of task_id, it is provided for checking the status and keeping track of the request made.

@bundle => false, as I am using the latest wsdl service of Signicat this functionality is not fully implemented at the moment and its removal causes the call to fail and nothing can be found about it in their currently available document, so setting its value to false will work.

Now we tell Signicat who would be signing the document. This is done by creating a subject block in the task.

1
2
3
4
subject: {
:@id => user.id,
"national-id" => user.cpr,
},

@id values will be used to identify the subject with. As I am using NEM_ID I used user CPR, if the person who is signing the document doesn’t match this CPR Signicat will not accept his sign. Exactly what you need to send to define the subject depends upon the method used for signing. Since I authenticated my user with Nemid I am also signing my document with the same method.

1
2
3
4
5
6
7
8
9
10
:attributes! => {"document-action" => {type: "sign",
"order-index" => 1, "send-result-to-archive" => false, optional: false}},
"document-action" => {
:attributes! => {
document: {
id: document_id, "ref-sds-id" => ref_sds_id,
"send-to-archive" => false,
'xsi:type'=> 'tns:sds-document'
}
},

Now we define what is to be done in the task on the document by creating the above block called the document-action.

Attributes in  SOAP call in RAILS is defined in the form shown below, and you will see that the code above is adhering to the format.

1
2
3
4
:attributes! => {foo: {all attributes of foo}}
foo:{
fields of foo
}

So we tell in our code that the type of document-action is sign and that we do not want to send the resulting document to be send to archive and order-index tells when to execute the particular action in case there are more than one action is defined. Since we are just signing a single document we only have one task and hence the value 1.

After the attributes we create the document block on which the action defined in document-action attributes will be performed.

Here we again have the document attributes in which the document unique identifier is present. Since we want to use the SDS provided we tell the document type to be sds-document, and ref_sds_id which we sat in the beginning of the function after uploading the file. And again I didn’t want to store the original file in the archive so it is set to value.

In the document there is only one field that I have used and that is document description.

1
2
3
4
document: {
description: 'description of file goes here'
}
},

and the part of document-action is completed. Now we create a block to tell about the kind of signature we will be using.

signature: { method: [“nemid-sign”] },

This is it, in signature we mentioned the method we would be using and that is nemid-sign. For complete list check the complete list of signature methods.

Now before we close the TASK block we will provide the url’s for each status of task i.e completion, postpone and cancellation. In our case its just the task completion.

1
2
3
"on-task-complete" => completion_callback_url
}],
}

I am putting all the closing brackets for the sake of closing the message block.

response.success? && response.body[:create_request_response]

now we check if we get a successful response if yes then we return the response.body[:create_request_reponse]

We store this in a variable let’s call it sign_request_response.

So the result will look something like this

1
2
sign_request_response = data_file.send_signature_request(current_user, task_id,
completion_callback_url)

Datafile here is the object of the model we created all the functions for.

Now we check sign_request_response exists and create the actual url where user will be signing the document.

1
2
3
4
5
if sign_request_response
sign_request_response[:request_id]}&task_id=#{task_id}"
redirect_to doc_signing_url
end

Both the codes(model function and the creation of doc_signing_url) are put together in a function called sign_document

def sign_document data_file = DataFile.find(params[:data_file_id]) base_url = ‘Applications base url’ completion_callback_url = “#{base_url}#{route to controller#index }?request_id=${requestId}” task_id = “#{current_user.id}-#{data_file.id}” response = data_file.send_signature_request(current_user, task_id, completion_callback_url) if sign_request_response doc_signing_url = “https://test.signicat.com/std/docaction/contracto.dk?request_id=#{ sign_request_response[:request_id]}&task_id=#{task_id}” redirect_to doc_signing_url end end

In this code the current_user is an application method that returns the session user.

the document is signed and by default you will receive DSIGXML.

What is done with this is entirely upto the application requirement. However I have used Signicat’s packaging method which creates a Signed PDF document.