Signing text or PDF documents - ASP.NET Core

This tutorial demonstrates how to you can easily add digital signature capabilities for PDF and plain text documents to your own application(s), by using Criipto Verify’s signing features.

Three steps are required to complete your first test login:

The tutorial will walk you through the technical details of the integration process between your application and Criipto Verify, and will also explain how to use test e-ID’s to validate your integration. To use real e-ID’s for signing, the integration is the same, but you must be set up for Production.

This sample uses a .NET Core backend and accompanying React.js frontend. If you are building your system on other platforms, you can do exactly the same in any system that supports JSON over HTTP.

Register Your Application in Criipto Verify

After you signed up for Criipto Verify, you must register an application before you can try requesting a signature. If you gaven’t already done this, go to our management dashboard on manage.criipto.id, and navigate to the Applications tab and create a new application.

The sample application for this tutorial uses the following settings. When you move outside the scope of the demo and start the integration from your own system, change the values accordingly so your system communicates with Criipto Verify using it’s dedicated settings.

Register App

Specifically, you will need the following information:

  • Client ID to identify your application to Criipto Verify. In the case below we chose urn:criipto:samples:no1
  • Domain on which you will be communicating with Criipto Verify. Could be for example samples.criipto.id
  • Callback URL on which your application expects the result of the signing process from Criipto Verify

Register Callback URLs

Before you can start sending requests to Criipto Verify, you need to pre-register the URL(s) on which you expect to receive the returned JSON Web Token (aka a JWT) on.

These URLs are known as Callback URLs, and they are links to your system. Criipto Verify will send the JWT containing the outcome of the document signing process only to a pre-registered Callback URL.

Callback URLs for the sample project are: http://localhost:5000/sign/callback and https://localhost:5001/sign/callback:

Callback URLs

Make sure to add your own Callback URLs. Put each URL on a new line.

If you deploy your application to a different URL you will also need to ensure to add that URL to Callback URLs.

PDF document signatures

PDF document signatures are currently supported for Norwegian BankID only.

There are three important steps in obtaining a signature:

  1. Upload a PDF to Criipto Verify and retrieve a redirect URI
  2. Redirect the user to the redirect URI
  3. Handle a callback from Criipto Verify

1. Upload a PDF to Criipto Verify and retrieve a redirect URI

A first step in obtaining a signature is to upload one or more PDF documents to Criipto Verify, which will respond with a redirect URI where you have to redirect the users browser to, so they can complete the signature process.

Endpoint: /sign/pdfv1

Query parameters:

  • wa - constant value: wsignin1.0
  • wtrealm - Your Criipto Verify Client ID
  • wreply - a signature callback URL for your application
  • wauth - acr_value of the authentication method. Currently only the Norwegian BankID, urn:grn:authn:no:bankid, may be used for PDF signing.
  • ui_locales - specify the UI language to use by authentication provider

Body parameters:

  • signProperties: Object<SignProperties>
  • documents: List<PdfDocument>

SignProperties class:

  • orderName - string displayed to the user as an instruction when starting the signature process
  • showConfirmation and showUnderstanding - booleans that controls some aspect of the native provider’s UX
    public class SignProperties {
      public string orderName {get; set;}
      public bool showConfirmation {get; set;}
      public bool showUnderstanding {get; set;}
    }
    

PdfDocument class:

  • description - string, per-document description displayed to the user
  • pdf - string, base64 encoded PDF document
  • seal - Object<PdfSeal>, specify an absolute position of the seal in the signed document
    public class PdfDocument {        
      public string description {get; set;}
      public string pdf {get; set;}
      public PdfSeal seal {get; set;}
    }
    

PdfSeal class:

  • x and y - integer, absolute position of the seal
  • page - number of the page where the seal will be applied to
    public class PdfSeal {
      public Int64 x {get; set;}
      public Int64 y {get; set;}
      public Int64 page {get; set;}
    }
    

Full URI example: {your_criipto_domain}/sign/pdfv1?wa=wsignin1.0&wtrealm={your_criipto_id}&wreply=https://localhost:5001/sign/callback&wauth=urn:grn:authn:no:bankid&ui_locales=en

Body example:

var body = new PdfSignRequest {
  signProperties = new SignProperties {
    orderName = "Demo signing",
    showConfirmation = true,
    showUnderstanding = true
  },
  documents = new List<PdfDocument> {
    new PdfDocument {
      description = "Demo document",
      pdf = "...base64-encoded PDF document contents (UTF8)...",
      seal = new PdfSeal {
        x = 10,
        y = 10,
        page = 1
      }
    }
  }
};

If successful, Criipto Verify will respond with a JSON literal with a redirectUri property:

{
  "redirectUri": "a URL that you must redirect the users browser to"
}

2. Redirect the users browser to the redirect URI

After you have successfully retrieved a redirect URI from Criipto Verify, you have to redirect the users browser to the redirect URI. The user will then be given the chance to review the document(s) and proceed to the actual signing.

3. Handle the callback from Criipto Verify

If signing was successful, a signature property will be posted to the Callback URL you specified in the first step, and it will contain a JWT.

Your system must now validate the JWT before consuming it for production purposes (such as storing it for bookkeeping purposes).

var validationParams = new TokenValidationParameters{
  ValidIssuer = discoveryDoc.Issuer,
  ValidAudience = _configuration["CriiptoVerify:ClientId"],
  IssuerSigningKeys = discoveryDoc.SigningKeys,
};

var tokenHandler = new JwtSecurityTokenHandler{
  InboundClaimTypeMap = new Dictionary<string, string>(),
  MaximumTokenSizeInBytes = int.MaxValue
};

SecurityToken validatedToken = null;
var claimsPrincipal = tokenHandler.ValidateToken(response.signature, validationParams, out validatedToken);
var jwtToken = validatedToken as JwtSecurityToken;

if (jwtToken != null) {
  ViewData["payload"] = jwtToken.RawPayload;
}

In the example above, jwtToken.RawPayload will contain information about the user that signed the document, and a base64-encoded PDF with the seal. Below is an example of a jwtToken.RawPayload retrieved from one of the test users.

{
  "iss": "https://easyid.www.prove.id",
  "aud": "urn:grn:app:easyid-signing-demo",
  "sub": "{df8a73f0-8e54-472d-987d-0d218ae6a62e}",
  "bankid_sub": "24ac2c8a-afe3-4e1e-ae49-87022adfccf2",
  "birthdate": "1960-07-24",
  "name": "Marko Bura",
  "bankid_altsub": "9578-6000-4-454370",
  "ssn": "24071462532",
  "uniqueuserid": "9578-6000-4-454370",
  "certissuer": "CN=BankID - TestBank1 - Bank CA 3,OU=123456789,O=TestBank1 AS,C=NO;OrginatorId=9980;OriginatorName=BINAS;OriginatorId=9980",
  "certsubject": "CN=Bura\\, Marko,O=TestBank1 AS,C=NO,SERIALNUMBER=9578-6000-4-454370",
  "evidence": [
    {
      "signedDocumentSha256": "pOIZryPECQB1nAtIfiw4Di7CFS5koXxrzDbbAMVKUKc=",
      "padesSignedPdf": "{base64_encoded_signed_pdf}"
      "description": "Demo document",
      "unsignedDocumentSha256": "Avhz9KkWRKzVZ9iRC9lGk5hWiFjk6hMHvBH5/uS9hbs="
    }
  ],
  "iat": 1600524372,
  "nbf": 1600524372,
  "exp": 1600524372
}

A base64-encoded signed PDF can be retrieved from jwtToken.RawPayload.evidence[0].padesSignedPdf.

Plain text signature

Plain text signature is supported for DK NemID, NO BankID and SE BankID.

There are two important steps in obtaining a signature:

  1. Redirect the user to the signature endpoint
  2. Handle a callback from Criipto Verify

1. Redirect the user to the signature endpoint

A first step is to construct a valid signature URL, and redirect the user to the Criipto Verify text signing endpoint: /sign/text

Query parameters:

  • wa - constant value: wsignin1.0
  • wtrealm - ˇYour Criipto Verify client ID
  • wreply - a signature callback URL for your application
  • wauth - acr_value of the authentication method you want to use. You can find the exact value(s) to use here
  • signtext - base64-encoded text to be signed
  • orderName - string displayed to the user as an instruction when starting a signature process by authentication provider
  • showUnderstanding and showConfirmation - booleans which control respective UI aspects of authentication provider
  • ui_locales - specify the UI language to use by authentication provider

Full signature URL example: {your_criipto_domain}/sign/pdfv1?wa=wsignin1.0&wtrealm={your_criipto_id}&wreply=https://localhost:5001/sign/callback&wauth=urn:grn:authn:no:bankid&signtext=VGhpcyBpcyBhbiBleGFtcGxlLg%3D%3D&orderName=Signing%20demo&showUnderstanding=true&showConfirmation=true&ui_locales=en

If a valid signature URL has been constructed, Criipto Verify will redirect the user to the authentication provider and handle signing, after which the user will be redirected to the callback URL, and a JWT will be posted to the callback route.

3. Handle a callback from Criipto Verify

If signing was successful, a signature property will be posted to the callback route you specified in the first step, and it will contain a JWT.

It’s recommended to validate a JWT before consuming it.

var validationParams = new TokenValidationParameters{
  ValidIssuer = discoveryDoc.Issuer,
  ValidAudience = _configuration["CriiptoVerify:ClientId"],
  IssuerSigningKeys = discoveryDoc.SigningKeys,
};

var tokenHandler = new JwtSecurityTokenHandler{
  InboundClaimTypeMap = new Dictionary<string, string>(),
  MaximumTokenSizeInBytes = int.MaxValue
};

SecurityToken validatedToken = null;
var claimsPrincipal = tokenHandler.ValidateToken(response.signature, validationParams, out validatedToken);
var jwtToken = validatedToken as JwtSecurityToken;

if (jwtToken != null) {
  ViewData["payload"] = jwtToken.RawPayload;
}

In the example above, jwtToken.RawPayload will contain information about the user who signed the text. Below is an example of a jwtToken.RawPayload retrieved from one of the test users.

{
  "iss": "https://easyid.www.prove.id",
  "aud": "urn:grn:app:easyid-signing-demo",
  "signtext": "VGhpcyBpcyBleGFtcGxlLg==",
  "sub": "{df8a73f0-8e54-472d-987d-0d218ae6a62e}",
  "bankid_sub": "24ac2c8a-afe3-4e1e-ae49-87022adfccf2",
  "birthdate": "1960-07-24",
  "name": "Marko Bura",
  "bankid_altsub": "9578-6000-4-454370",
  "ssn": "24071462532",
  "ocspResponse": "{ocsp_response}",
  "uniqueuserid": "9578-6000-4-454370",
  "certissuer": "CN=BankID - TestBank1 - Bank CA 3,OU=123456789,O=TestBank1 AS,C=NO;OrginatorId=9980;OriginatorName=BINAS;OriginatorId=9980",
  "certsubject": "CN=Bura\\, Marko,O=TestBank1 AS,C=NO,SERIALNUMBER=9578-6000-4-454370",
  "evidence": "{evidence}",
  "iat": 1600528255,
  "nbf": 1600528255,
  "exp": 1600528255
}

Test users

Almost all e-ID types have a notion of test users and real users.

Real users are real people logging in to a web site, thus voluntering their real name and typically also a social security number, SSN.

Test users are either created by you for the occasion, or we provide you with access to already created test users.

You may ready more in the section on test users

Setting up for Production

Once you have integrated with Criipto Verify and tested that it works with test user accounts, you are ready to go to production to accept real e-ID logins and signatures.

Please note that for production usage a paid subscription is required.

Read more in the section on how to set up for production.

List of acr_values

Criipto Verify supports a range of country and bank specific e-ID services. They are all accessed through the same endpoints, e.g. https://<YOUR COMPANY>.criipto.id/oauth2/authorize

To pick the login method you must set the acr_values parameter on the authentication request in order to choose the type of authentication you want. How you set this query string parameter varies with programming platform and your OpenID Connect library of choice.

The current list of possible values is:

Norwegian BankID  
  Mobile or Web (user choice):  urn:grn:authn:no:bankid
Norwegian Vipps Login  
  Login with Vipps app:  urn:grn:authn:no:vipps
Swedish BankID  
  Same device: urn:grn:authn:se:bankid:same-device
  Another device (aka mobile):  urn:grn:authn:se:bankid:another-device
Danish NemID  
  Personal with code card:  urn:grn:authn:dk:nemid:poces
  Employee with code card:  urn:grn:authn:dk:nemid:moces
  Employee with code file:  urn:grn:authn:dk:nemid:moces:codefile
Finish e-ID  
  BankID: urn:grn:authn:fi:bankid
  Mobile certificate (Mobiilivarmenne):  urn:grn:authn:fi:mobile-id
  Any of the two: urn:grn:authn:fi:all