Sist endret: 28. feb. 2025

Brukerstyrt signering

Følg disse stegene for å implementere brukerstyrt signering i din tjeneste.

Hva betyr brukerstyrt signering?

Med brukerstyrt signering mener vi en app hvor de som skal signere tildeles rettigheter underveis i utfyllingen av skjemaet.

Typisk vil appen be skjemaeier om å oppgi fødselsnummer og etternavn for de de personene som skal signere, eventuel organisasjosnummer dersom noen med rett til å signere i en bedrift som skal signere.

Når skjemaet er ferdig fylt ut, trykker man på “Send til signering”. Da vil appen delegere rettigheter og sende en melding til innboksen i Altinn. Vedkommende vil også kunne varsles via e-post og sms, avhengig av konfigurasjon.

Avhengigheter

Brukerstyrt signering avhenger av Meldingstjenesten (Correspondence) i Altinn, som krever eget oppsett.

Melding brukes for å gi beskjed til signatar om at de har blitt bedt om å signere et skjema i Altinn, og for å sende signeringskvittering til innboksen ders når signeringen er utført.

Se hvordan du kommer i gang med det i denne guiden.

Eksempel på konfigurasjon

I følgende repo ligger det eksempel på en applikasjon med brukerstyrt signering.

Hovedflyten i applikasjonen er:

  1. Utfyller av skjema oppgir fødselsnummer og etternavn for personer eller organisasjonsnummer for organisasjoner som skal signere.
  2. Når skjema er ferdig utfylt trykker utfyller “Til signering”, som beveger prosessen til neste steg i prosessen, som er signeringssteget.
  3. I det signeringssteget initialiseres kaller appen en implementasjon av interfacet ISigneeProvider, som dere må implementere, for å finne ut hvem som må få delegert tilgang til å signere.
  4. Signatarene får delegert rettigheter og mottar notifikasjon om at de har en signeringsoppgave.
  5. Signatarene finner skjemaet i sin innboks, åpner det, ser over data og trykker signer.
  6. Innsender signerer også, dersom appen satt opp slik, og sender deretter inn skjemaet. Automatisk innsending er p.t. ikke søttet.

Nedenfor følger de viktiste konfigurasjonsstegene for å få satt opp en slik applikasjon.

1. Legg til en signeringsoppgave i appens prosess, med tilhørende konfigurasjon

    Utvid app prossesen med signing task:

    Det må legges til et signeringssteg i appens prosess, som er definert i App/config/process/process.bpmn.

    Det anbefales å dra inn prosessteget via prosessdesigneren i Altinn Studio. Da får man generert BPMN-diagram som viser flyten i appen. Forløpig vil prosessdesigneren bare delvis konfigurere steget riktig, så det må suppleres med manuell konfigurasjon.

    Signering benytter to burkerhandlinger (user actions):

    • sign: Selve signeringshandlingen.
    • reject: Dersom det skal være mulig å avbryte signeringssteget, så må det også legges til en gateway for å styre hvor prosessen skal gå videre når det skjer.

    Dersom Altinn-brukergrensesnittet benyttes av appen, så vil handlingene ovenfor bli kalt ved knappetrykk i signeringssteget. Om kun API-et benyttes, så må disse kalles manuelt via /actions-endepunktet eller process next.

    Et signeringssteg kan se omtrent slik ut:

    <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>
    
                    <!-- De faktiske dataene som skal signeres. Kan være vedlegg, skjemadata i xml, eller PDF fra tidligere steg. -->
                    <altinn:dataTypesToSign>
                        <altinn:dataType>ref-data-as-pdf</altinn:dataType>
                    </altinn:dataTypesToSign>
    
                    <!-- Denne datatypen brukes for lagre signaturene. -->
                    <altinn:signatureDataType>signatures</altinn:signatureDataType>
    
                    <!-- Denne datatypen brukes for lagre signatarene og tilhørende informasjon. -->
                    <altinn:signeeStatesDataTypeId>signeeState</altinn:signeeStatesDataTypeId>
    
                    <!-- Denne ID-en angir hvilken implementasjon av C# interface-et ISigneeProvider som skal benyttes for dette signeringssteget. -->
                    <altinn:signeeProviderId>signees</altinn:signeeProviderId>
    
                    <!-- Her oppgis en correspondence resource, som brukes for å si fra til signaterene om at de må inn og signere, samt signeringskvittering. -->
                    <altinn:correspondenceResource>app-correspondence-resource</altinn:correspondenceResource>
    
                    <!-- Dersom man ønsker at det skal bli generert en PDF av signeringssteget så kan man oppgi en datatype her av type application/pdf. -->
                    <altinn:signingPdfDataType>signing-step-pdf</altinn:signingPdfDataType> <!-- optional -->
    
                </altinn:signatureConfig>
            </altinn:taskExtension>
        </bpmn:extensionElements>
        <bpmn:incoming>SequenceFlow_1oot28q</bpmn:incoming>
        <bpmn:outgoing>SequenceFlow_1if1sh9</bpmn:outgoing>
    </bpmn:task>
    

    Legg til datatyper for å lagre signeringsdata

    Disse datatypene legger til i dataTypes i App/config/applicationmetadata.json.

    Den første datatypen benyttes av signeringssteget til å lagre de faktiske signaturene som genereres når brukeren utfører signeringshandlingen.

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

    Denne datatypen benyttes for å lagre informasjon om signatarene som skal få deligert rettigheter til å signere og statusen deres.

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

    Det er viktig å sette allowedContributers til "app:owned". Det gjør at disse dataene ikke kan redigeres via appens API, men kun av appen selv.

    Datatypenes ID-er kan settes til noe annet, men det må matche ID-ene som legges inn i signatureDataType og signeeStatesDataTypeId i prossessteget, som vist i punktet under.

    Tilgangsstyring

    Gi read, write og eventuelt sign til den som fyller ut skjemaet.

    For at appen skal kunne delegere rettigheter til de som skal signere så må appen få rettigheter til å delegere read og sign. Se eksempel nedenfor.

    • Bytt ut ttd med riktig org.
    • Bytt ut app_ttd_signering-brukerstyrt med tilsvarende app_{org}_{appnavn}.
    • Bytt ut signering-brukerstyrt med appnavn.
    
    <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>
    

    Dette steget må gjøres manuelt. Støtte for konfigurasjon i Altinn Studio kommer senere. Se “Manuelt oppsett”-fanen for denne seksjonen for veiledning.

    2. Legg til layout-set for signering

      Legg til en ny mappe under App/ui for signeringsoppgaven din. Kall den f.eks. signing.

      Oppdater filen App/ui/layout-sets.json med ny sidegruppe, som har samme id som mappen du nettopp opprettet.

      Din oppdaterte layout-sets.json kan se slik ut:

      {
        "$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"
            ]
          }
        ]
      }
      

      I mappen du opprettet, legg til en ny fil kalt signing.json.

      Det finnes et sett med ferdige komponenter for å bygge opp layout for et signeringssteg. Vi anbefaler å bruke disse, men de er ikke obligatoriske.

      • SigneeList:
        • Lister ut signatarer og tilhørende signeringsstatus.
      • SigningDocumentList:
        • Lister ut dataene som blir signert på. Feks. vedlegg, xml-data eller PDF-oppsummering fra tidligere steg.
      • SigningStatusPanel:
        • Utleder status for signeringssteget og viser relevante knapper til sluttbruker, feks. “Signer”-knappen.

      Dersom du ikke benytter SigningStatusPanel for å vise “Signer”-knappen, så må du legge til en egen action button med action “sign”, for å la sluttbruker signere.

      Eksempel på bruk av komponentene:

      {
        "$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"
          }
        ]
        }
      }
      

      Dette steget må gjøres manuelt. Støtte for konfigurasjon i Altinn Studio kommer senere. Se “Manuelt oppsett”-fanen for denne seksjonen for veiledning.

      3. Legg til tekstressurser

        Legg til en teksressurs-fil under ´App/config/texts´ for hvert språk du vil støtte.

        Her definerer du tekstressurser som skal brukes i kommunikasjonen med brukerene.

        Tekstressurs-ID-er for signeringsflyten settes opp for å overstyre innholdet i meldinger som blir sendt til Altinn-innboksen.

        signing.correspondence_cta_title - tittel på varselmelding til signatar
        signing.correspondence_cta_summary - undertittel på varselmelding signatar
        signing.correspondence_cta_body - innhold i varselmelding signatar

        signing.correspondence_receipt_title - tittel på kvitteringsmelding signing.correspondence_receipt_summary - undertittel på kvitteringsmelding signing.correspondence_receipt_body - innhold i kvitteringsmelding

        Du kan også sette opp tekstressurser for å overstyre innholdet i sms og e-post som sendes for å varsle signatar og signeringsoppgave. Disse kan du kalle hva du vil, og koble dem opp til notification implementasjonen i neste punkt (punkt 4.).

        Eksempel på tekstressurser for varsling med egne tekster for e-post, samt kvittering:

        
            {
              "id": "signing.correspondence_receipt_title",
              "value": "Kvittering: Signering av stiftelsesdokumenter"
            },
            {
              "id": "signing.correspondence_receipt_summary",
              "value": "Du har signert stiftelsesdokumentene"
            },
            {
              "id": "signing.correspondence_receipt_body",
              "value": "Dokumentene du har signert er vedlagt. Disse kan lastes ned om ønskelig. <br /><br />Hvis du lurer på noe, ta kontakt med Brønnøysundregistrene på telefon 75 00 75 00."
            },
            {
              "id": "signing.correspondence_cta_title",
              "value": "Oppgave - Signer stiftelsesdokumenter"
            },
            {
              "id": "signing.correspondence_cta_summary",
              "value": "Du har blitt lagt til som signatar."
            },
            {
              "id": "signing.correspondence_cta_body",
              "value": "Du har blitt lagt til som signatar for stiftelsesdokumenter. <br /> $InstanceUrl <br /><br />"
            },
            {
              "id": "signing.notification_content",
              "value": "Hei {0},\n\nDu har mottatt stiftelsesdokumenter for signering i Altinn. Logg inn på Altinn for å signere dokumentene.\n\nMed vennlig hilsen\nBrønnøysundregistrene"
            },
            {
              "id": "signing.email_subject",
              "value": "Stiftelsesdokumenter mottatt for signering i Altinn. Gå til Altinn-innboks for å signere."
            },
        

        Dette steget må gjøres manuelt. Støtte for konfigurasjon i Altinn Studio kommer senere. Se “Manuelt oppsett”-fanen for denne seksjonen for veiledning.

        4. Oppgi hvem som skal signere

          For at appen skal vite hvem som skal få tilganger for å lese og signere må C# interface-et ISigneeProvider implementeres.

          Den må returnere et sett med personer og/eller virksomheter som skal få rettighetene. Det kan for eksempel være basert på datamodellen, som vist nedenfor. Id-attributtet i denne implementasjonen må matche ID som ble angitt i <altinn:signeeProviderId>.

          #nullable enable
          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)
                      );
              }
          }
          

          Dette steget må gjøres manuelt. Støtte for konfigurasjon i Altinn Studio kommer senere. Se “Manuelt oppsett”-fanen for denne seksjonen for veiledning.

          5. Testing

          Obs! Foreløpig er det ikke laget mocking av delegasjon i localtest, så testing må i praksis utføres i TT02-miljøet.

          Det er caching i autorisasjonslaget som gjør at det kan ta tid før en bruker som har fått delegert tilgang til et skjema via brukerstyrt signering ser skjemaet i sin Altinn innboks.

          Men dette vil altså bare inntreffe for:

          • De brukerne som er aktivt pålogget Altinn når instansdelegeringen skjer
          • Ikke allerede har annen tilgang for InstanceOwner

          For å unngå å oppleve dette under testing kan man delegere til en person man ikke har brukt i testing den siste timen.