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:
- Utfyller av skjema oppgir fødselsnummer og etternavn for personer eller organisasjonsnummer for organisasjoner som skal signere.
- Når skjema er ferdig utfylt trykker utfyller “Til signering”, som beveger prosessen til neste steg i prosessen, som er signeringssteget.
- 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. - Signatarene får delegert rettigheter og mottar notifikasjon om at de har en signeringsoppgave.
- Signatarene finner skjemaet i sin innboks, åpner det, ser over data og trykker signer.
- 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 tilsvarendeapp_{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 signatarsigning.correspondence_cta_summary
- undertittel på varselmelding signatarsigning.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.