Last modified: Feb 28, 2025

Runtime delegated signing

Follow these steps to implement runtime delegated signing in your service

What does runtime delegated singing mean?

By runtime delegated signing, we mean an app where those who need to sign are granted rights during the form-filling process.

Typically, the app will ask the form owner to provide the social security number and last name of the individuals who need to sign, or alternatively, the organization number if someone with signing authority within a company needs to sign.

Once the form is fully completed, the user presses “Submit for singing”. The app will then delegate rights and send a message to the inbox in Altinn. The recipient may also be notified via email and SMS, depending on the configuration.

Prerequisites

Runtime deleaged singing depends on then message service (Correspondence) in Altinn, which requires separate configuration.

The message service is used to tell the signee that they have been asked to sign a form in Altinn, and to send them a receipt of what they signed when the signature has been submitted.

See how to get started in this guide.

Example Application

In the following repository, you can find an example of an application with user-driven signing.

The main flow is:

  1. The form filler enters the personal identification number and last name of the individuals who need to sign, or alternatively, the organization number if it is a company.
  2. Once the form is completed, the filler clicks a “Go to signing” button, which moves the process to the signing step.
  3. During the signing step, the application calls an implementation of the interface ISigneeProvider, which you must implement, to determine who should be delegated access to sign.
  4. The signers are delegated rights and receive a notification that they have a signing task.
  5. The signers find the form in their inbox, open it, review the data, and click “Sign.”
  6. The submitter also signs if the app is configured this way and then submits the form. Automatic submission is currently not supported.

Below are the key configuration steps for setting up such an application.

1. Add and configure a singing task in the app process

    Extend the Application Process with a Signing Task

    A signing task must be added to App/config/process/process.bpmn, as shown in the example below.

    We recommend doing this using the Altinn Studio process editor, so that the BPMN diagram is generated, to show the apps process. However, as of now, the process editor will only partially configure this task correctly, so some manual adjustments are required.

    Signing uses two user actions.

    • sign: The actual signing action.
    • reject: If the signing step should be cancellable, a gateway must also be added to control where the process should continue. See the example below.

    If the Altinn user interface is used by the application, these actions will be triggered by button clicks in the signing stage. If only the API is used, they must be triggered manually via the /actions endpoint or the process next endpoint.

    <bpmn:task id="SigningTask" name="Signering">
      <bpmn:extensionElements>
        <altinn:taskExtension>
          <altinn:taskType>signing</altinn:taskType>
          <altinn:actions>
            <altinn:action>sign</altinn:action>
            <altinn:action>reject</altinn:action>
          </altinn:actions>
          <altinn:signatureConfig>
            <!-- The actual data that should be signed. Can be attachments, the form data in xml or PDF from earlier step. -->
            <altinn:dataTypesToSign>
              <altinn:dataType>ref-data-as-pdf</altinn:dataType>
            </altinn:dataTypesToSign>
    
            <!-- This data type is used to store the signatures -->
            <altinn:signatureDataType>signatures</altinn:signatureDataType>
    
            <!-- This data type is used to store the signees and related information -->
            <altinn:signeeStatesDataTypeId>signeesState</altinn:signeeStatesDataTypeId>
    
            <!-- This ID tells the app which implementation of the C# interface ISigneeProvider that should be used for this singing step -->
            <altinn:signeeProviderId>signees</altinn:signeeProviderId>
    
            <!-- If you want a PDF summary of the singing step, enter a datatype of type application/pdf here -->
            <altinn:signingPdfDataType>signing-step-pdf</altinn:signingPdfDataType> <!-- optional -->
    
            <!-- If the signee should receive a receipt with the documents that were signed in their Altinn inbox, enter a correspondence resource her. Setup of this is documented separately. -->
            <altinn:correspondenceResource>app-correspondence-resource</altinn:correspondenceResource> <!-- optional -->
    
          </altinn:signatureConfig>
        </altinn:taskExtension>
      </bpmn:extensionElements>
      <bpmn:incoming>SequenceFlow_1oot28q</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_1if1sh9</bpmn:outgoing>
    </bpmn:task>
    

    These data types should be added to dataTypes in App/config/applicationmetadata.json.

    The first data type is used by the signing stage to store the actual signatures generated when a user completes the signing action.

    {
        "id": "signatures",
        "allowedContentTypes": [
            "application/json"
        ],
        "allowedContributers": [
            "app:owned"
        ]
    }
    

    This data type is used to store information about the signers who should be delegated signing rights and their status.

    {
        "id": "signeeState",
        "allowedContentTypes": [
            "application/pdf"
        ],
        "allowedContributers": [
            "app:owned"
        ],
        "maxCount": 1,
        "minCount": 0
    }
    

    It is important to set allowedContributers to "app:owned". This ensures that these data items cannot be edited via the app’s API but only by the app itself.

    The IDs of the data types can be changed, but they must match the IDs set in signatureDataType and signeeStatesDataTypeId in the process step, as shown in the next section.

    Access control

    Give readm write and optionally sign to the role that should fill out the form.

    In order for the service to be able to delegate access rights to the signees, the app needs to have the right to delegate the read and sign actions. Below is an example which you can use in your policy.xml file.

    • Replace ttd with the correct org.
    • Replace app_ttd_signering-brukerstyrt with your own org and app name inserted into this template: app_{org}_{app-name}.
    • Replace signering-brukerstyrt with your app name
    • Modify the RuleId attribute to fit nicely into your policy.xml.
    <xacml:Rule RuleId="urn:altinn:org:ttd:signering-brukerstyrt:ruleid:7" Effect="Permit">
      <xacml:Description>
          A rule defining all instance delegation rights the App itself is allowed to perform for instances of the app ttd/signering-brukerstyrt. In this example the app can delegate the Read and Sign actions for task SingingTask.
      </xacml:Description>
      <xacml:Target>
          <xacml:AnyOf>
              <xacml:AllOf>
                  <xacml:Match
                      MatchId="urn:oasis:names:tc:xacml:3.0:function:string-equal-ignore-case">
                      <xacml:AttributeValue
                          DataType="http://www.w3.org/2001/XMLSchema#string">
                          app_ttd_signering-brukerstyrt
                      </xacml:AttributeValue>
                      <xacml:AttributeDesignator
                          AttributeId="urn:altinn:resource:delegation"
                          Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
                          DataType="http://www.w3.org/2001/XMLSchema#string"
                          MustBePresent="false"
                      />
                  </xacml:Match>
              </xacml:AllOf>
          </xacml:AnyOf>
          <xacml:AnyOf>
              <xacml:AllOf>
                  <xacml:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
                      <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">ttd</xacml:AttributeValue>
                      <xacml:AttributeDesignator
                          AttributeId="urn:altinn:org"
                          Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource"
                          DataType="http://www.w3.org/2001/XMLSchema#string"
                          MustBePresent="false"
                      />
                  </xacml:Match>
                  <xacml:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
                      <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">signering-brukerstyrt</xacml:AttributeValue>
                      <xacml:AttributeDesignator
                          AttributeId="urn:altinn:app"
                          Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource"
                          DataType="http://www.w3.org/2001/XMLSchema#string"
                          MustBePresent="false"
                      />
                  </xacml:Match>
                  <xacml:Match
                      MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
                      <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">SingingTask</xacml:AttributeValue>
                      <xacml:AttributeDesignator
                          AttributeId="urn:altinn:task"
                          Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource"
                          DataType="http://www.w3.org/2001/XMLSchema#string"
                          MustBePresent="false"
                      />
                  </xacml:Match>
              </xacml:AllOf>
          <xacml:AnyOf>
              <xacml:AllOf>
                  <xacml:Match MatchId="urn:oasis:names:tc:xacml:3.0:function:string-equal-ignore-case">
                      <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">read</xacml:AttributeValue>
                      <xacml:AttributeDesignator
                          AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id"
                          Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action"
                          DataType="http://www.w3.org/2001/XMLSchema#string"
                          MustBePresent="false"
                      />
                  </xacml:Match>
              </xacml:AllOf>
              <xacml:AllOf>
                  <xacml:Match MatchId="urn:oasis:names:tc:xacml:3.0:function:string-equal-ignore-case">
                      <xacml:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">sign</xacml:AttributeValue>
                      <xacml:AttributeDesignator
                          AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id"
                          Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action"
                          DataType="http://www.w3.org/2001/XMLSchema#string"
                          MustBePresent="false"
                      />
                  </xacml:Match>
              </xacml:AllOf>
          </xacml:AnyOf>
      </xacml:Target>
    </xacml:Rule>
    

    This step needs to be done manually. Support for configuration in Altinn Studio Designer will come later. Take a look at the “Manual setup”-tab for this section for guidance.

    2. Add a singing layout set

      Add a folder under App/ui for your singing task called signing or some other logical name. Update App/ui/layout-sets.json with a new page group, using the same id as the folder you just created.

        {
          "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout-sets.schema.v1.json",
          "sets": [
            {
              "id": "form",
              "dataType": "model",
              "tasks": [
                "Task_1"
              ]
            },
            {
              "id": "signing",
              "dataType": "model",
              "tasks": [
                "SigningTask"
              ]
            }
          ]
        }
      

      In the folder you created, add a new file called signing.json.

      There are standard components that can be used to build a layout set for a signing step. You are not required to use these components, but it’s recommended.

      • SigneeList:
        • Lists the signees and their signing status.
      • SigningDocumentList:
        • Lists the data being signed. For example attachments, xml-data or a PDF summary from an earlier step.
      • SigningStatusPanel:
        • Determines the current status of the singing task and present relevant information and buttons to the end user, for instance the “Sign”-button.

      If you choose not to use the SingingStatusPanel to display the “Sign”-button, you must as a minimum add an action button with action sign, to allow the end user to sign.

      Example of usage of the standard components:

      {
          "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
          "data": {
            "layout": [
            {
              "id": "headerSigningFounders",
              "type": "Header",
              "size": "L",
              "textResourceBindings": {
                "title": "Her kan man ha en overskrift"
              }
            },
            {
              "id": "signee-list",
              "type": "SigneeList",
              "textResourceBindings": {
                "title": "Personer som skal signere",
                "description": "Personer som skal signere beskrivelse",
                "help": "Dette er personer som skal signere"
              }
            },
            {
              "id": "signing-documents",
              "type": "SigningDocumentList",
              "textResourceBindings": {
                "title": "Dokumenter som skal signeres",
                "description": "Dokumenter som skal signeres beskrivelse"
              }
            },
            {
              "id": "signing-state",
              "type": "SigningStatusPanel"
            }
          ]
          }
        }
      

      This step needs to be done manually. Support for configuration in Altinn Studio Designer will come later. Take a look at the “Manual setup”-tab for this section for guidance.

      3. Setup text resources

        Add a text resource file under ‘App/config/texts’ for each language you want to support.

        Here you define text resources to be used in communication with the user.

        Text resource IDs for the signing flow are set up to override the content in messages sent to the Altinn inbox.

        signing.correspondence_cta_title - title of notification message to signer
        signing.correspondence_cta_summary - subtitle of notification message to signer
        signing.correspondence_cta_body - content of notification message to signer

        signing.correspondence_receipt_title - title of receipt message signing.correspondence_receipt_summary - subtitle of receipt message signing.correspondence_receipt_body - content of receipt message

        You can also set up text resources to override the content in SMS and email sent to notify the signer of a signing task. You can name these whatever you want and connect them to the notification implementation in the next step (step 4).

        Example of text resources for notifications with custom texts for email, as well as receipt:

        {
          "id": "signing.correspondence_receipt_title",
          "value": "Receipt: Signing of founding documents"
        },
        {
          "id": "signing.correspondence_receipt_summary",
          "value": "You have signed the founding documents"
        },
        {
          "id": "signing.correspondence_receipt_body",
          "value": "The documents you have signed are attached. These can be downloaded if desired. <br /><br />If you have any questions, contact the Brønnøysund Register Centre at phone 75 00 75 00."
        },
        {
          "id": "signing.correspondence_cta_title",
          "value": "Task - Sign founding documents"
        },
        {
          "id": "signing.correspondence_cta_summary",
          "value": "You have been added as a signer."
        },
        {
          "id": "signing.correspondence_cta_body",
          "value": "You have been added as a signer for founding documents. <br /> $InstanceUrl <br /><br />"
        },
        {
          "id": "signing.notification_content",
          "value": "Hello {0},\n\nYou have received founding documents for signing in Altinn. Log in to Altinn to sign the documents.\n\nBest regards,\nBrønnøysund Register Centre"
        },
        {
          "id": "signing.email_subject",
          "value": "Founding documents received for signing in Altinn. Go to Altinn inbox to sign."
        },
        

        This step needs to be done manually. Support for configuration in Altinn Studio Designer will come later. Take a look at the “Manual setup”-tab for this section for guidance.

        4. Tell the app who the signees are

          To allow the app to determine who should receive access to read and sign, the C# interface ISigneeProvider must be implemented.

          The implementation must return a set of individuals and/or organizations that should receive rights to sign. This can be based on the data model, as shown in the example below.

          The Id property in this implementation must match the ID specified in altinn:signeeProviderId.

          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Threading.Tasks;
          using Altinn.App.Core.Features.Signing.Interfaces;
          using Altinn.App.Core.Features.Signing.Models;
          using Altinn.App.Core.Internal.Data;
          using Altinn.App.Core.Models;
          using Altinn.App.Models.Skjemadata;
          using Altinn.Platform.Storage.Interface.Models;
          
          namespace Altinn.App.logic;
          
          public class FounderSigneesProvider : ISigneeProvider
          {
              private readonly IDataClient _dataClient;
          
              public FounderSigneesProvider(IDataClient dataClient)
              {
                  _dataClient = dataClient;
              }
          
              public string Id { get; init; } = "founders";
          
              public async Task<SigneesResult> GetSigneesAsync(Instance instance)
              {
                  Skjemadata formData = await GetFormData(instance);
          
                  List<ProvidedSignee> providedSignees = [];
                  foreach (StifterPerson stifterPerson in formData.StifterPerson)
                  {
                      var personSignee = new PersonSignee
                      {
                          FullName = string.Join(
                              " ",
                              [stifterPerson.Fornavn, stifterPerson.Mellomnavn, stifterPerson.Etternavn]
                          ),
                          SocialSecurityNumber = stifterPerson.Foedselsnummer?.ToString() ?? string.Empty,
                          Notifications = new Notifications
                          {
                              OnSignatureAccessRightsDelegated = new Notification
                              {
                                  Email = new Email
                                  {
                                      EmailAddress = stifterPerson.Epost,
                                      SubjectTextResourceKey = "signing.email_subject",
                                      BodyTextResourceKey = "signing.notification_content".Replace(
                                          "{0}",
                                          stifterPerson.Fornavn
                                      ),
                                  },
                                  Sms = new Sms
                                  {
                                      MobileNumber = stifterPerson.Mobiltelefon,
                                      BodyTextResourceKey = "signing.notification_content".Replace(
                                          "{0}",
                                          stifterPerson.Fornavn
                                      ),
                                  }
                              }
                          }
                      };
          
                      providedSignees.Add(personSignee);
                  }
          
                  foreach (StifterVirksomhet stifterVirksomhet in formData.StifterVirksomhet)
                  {
                      var organisationSignee = new OrganisationSignee
                      {
                          Name = stifterVirksomhet.Navn,
                          OrganisationNumber =
                              stifterVirksomhet.Organisasjonsnummer?.ToString() ?? string.Empty,
                          Notifications = new Notifications
                          {
                              OnSignatureAccessRightsDelegated = new Notification
                              {
                                  Email = new Email
                                  {
                                      EmailAddress = stifterVirksomhet.Epost,
                                      SubjectTextResourceKey = "signing.email_subject",
                                      BodyTextResourceKey = "signing.notification_content".Replace(
                                          "{0}",
                                          stifterVirksomhet.Navn
                                      ),
                                  },
                                  Sms = new Sms
                                  {
                                      MobileNumber = stifterVirksomhet.Mobiltelefon,
                                      BodyTextResourceKey = "signing.notification_content".Replace(
                                          "{0}",
                                          stifterVirksomhet.Navn
                                      ),
                                  }
                              }
                          }
                      };
          
                      providedSignees.Add(organisationSignee);
                  }
          
                  return new SigneesResult { Signees = providedSignees };
              }
          
              private async Task<Skjemadata> GetFormData(Instance instance)
              {
                  DataElement modelData = instance.Data.Single(x => x.DataType == "Skjemadata");
                  InstanceIdentifier instanceIdentifier = new(instance);
          
                  return (Skjemadata)
                      await _dataClient.GetFormData(
                          instanceIdentifier.InstanceGuid,
                          typeof(Skjemadata),
                          instance.Org,
                          instance.AppId,
                          instanceIdentifier.InstanceOwnerPartyId,
                          new Guid(modelData.Id)
                      );
              }
          }
          

          This step needs to be done manually. Support for configuration in Altinn Studio Designer will come later. Take a look at the “Manual setup”-tab for this section for guidance.

          5. Testing

          Note: Fow now, delegation mocking is not implemented in local testing, so testing must be performed in the TT02 environment.

          Authorization caching may cause delays in users seeing delegated forms in their Altinn inbox if they were logged in when delegation occurred. To avoid this, delegate access to a user not used in testing for the last hour.