Skip to content

Latest commit

 

History

History
186 lines (143 loc) · 7.4 KB

File metadata and controls

186 lines (143 loc) · 7.4 KB

Authentication Methods

PostGuard requires the sender to prove their identity before encrypting. The SDK supports three authentication methods, each suited to a different environment.

Comparison

Method Environment Interactive
pg.sign.apiKey() Server-side, trusted clients No
pg.sign.yivi() Browser apps Yes (QR code)
pg.sign.session() Extensions, custom flows Depends on callback

API Key

Uses a pre-shared API key (prefixed with PG-) to authenticate with the PKG. Keys are issued by the postguard-business portal. Suitable for server-side applications or trusted client environments where you don't need interactive identity verification.

The SvelteKit example uses an API key for encryption:

const sealed = pg.encrypt({
  files,
  recipients: [
    pg.recipient.email(citizen.email),
    pg.recipient.emailDomain(organisation.email)
  ],
  sign: pg.sign.apiKey(apiKey),
  onProgress,
  signal: abortController?.signal
});

const result = await sealed.upload({
  notify: {
    recipients: true,
    message: message ?? undefined,
    language: 'EN'
  }
});

Source: encryption.ts#L24-L44

The SDK sends the API key as a Bearer token in the Authorization header when requesting signing keys from the PKG at POST /v2/irma/sign/key.

::: info API keys are part of PostGuard for Business. Contact your PKG administrator to obtain one. :::

Yivi Web

Runs an interactive Yivi session directly in the browser. The SDK renders a QR code (or app link on mobile) in the specified DOM element. The user scans it with the Yivi app to prove their email address.

For decryption in a browser, pass element to opened.decrypt() with a CSS selector for the Yivi QR container. See Decryption for the full call shape.

The PostGuard website uses Yivi signing with optional extra attributes and sender encryption:

const sign = pg.sign.yivi({
  element: '#crypt-irma-qr',
  attributes: [
    { t: 'pbdf.gemeente.personalData.fullname', optional: true },
    { t: 'pbdf.sidn-pbdf.mobilenumber.mobilenumber', optional: true },
    { t: 'pbdf.gemeente.personalData.dateofbirth', optional: true },
  ],
  includeSender: true,
});

The sender's email is always requested automatically. Attributes marked optional: true are presented to the user as optional disclosures: the user can choose to skip them during the Yivi session. Non-optional attributes must be disclosed for the session to succeed.

When includeSender is true, the sender's identity is added to the encryption policy so the sender can also decrypt their own message.

Parameters

Parameter Type Required Description
element string Yes CSS selector for the QR code container
senderEmail string No The sender's email address to prove
attributes Array<{ t, v?, optional? }> No Extra attributes to request (e.g. name, phone). Email is always included automatically.
includeSender boolean No Also encrypt for the sender so they can decrypt their own message (default: false)

Session Callback

The most flexible method. You provide a callback function that receives a session request and must return a JWT string. This lets you handle the Yivi session yourself: in a popup window, a separate process, or any custom flow.

The Thunderbird addon uses this for both encryption and decryption. For encryption, the session callback opens a Yivi popup:

const sealed = pg!.encrypt({
  sign: pg!.sign.session(
    async ({ con, sort }) => createYiviPopup(con as AttributeCon, sort as KeySort),
    { senderEmail: from }
  ),
  recipients: pgRecipients,
  data: mimeData,
});

Source: background.ts#L372-L379

For decryption, the same pattern with a session callback:

const opened = pg.open({ data: ciphertext });
const result = await opened.decrypt({
  recipient: myAddresses[0],
  session: async ({ con, sort, hints, senderId }) => {
    return createYiviPopup(
      con as AttributeCon,
      sort as KeySort,
      hints as AttributeCon | undefined,
      senderId
    );
  },
}) as DecryptDataResult;

Source: background.ts#L710-L721

The callback receives

Property Type Description
con Array<{ t, v? }> Attribute constraints the user must prove
sort 'Signing' | 'Decryption' Whether this is for signing or decryption
hints Array<{ t, v? }> Optional hints (e.g. expected recipient email)
senderId string Optional sender identifier

The callback must return a JWT string from a completed Yivi session.

Outlook dialog pattern

The Outlook addon uses Office.context.ui.displayDialogAsync() instead of browser.windows.create():

async function openYiviDialogForSigning(con: AttributeCon): Promise<string> {
  const dialogData = {
    hostname: PKG_URL,
    header: PG_CLIENT_HEADER,
    con,
    sort: "Signing",
    validity: secondsTill4AM(),
  };

  const encodedData = encodeURIComponent(JSON.stringify(dialogData));
  const dialogUrl = `${window.location.origin}/dialog.html?data=${encodedData}`;

  return new Promise<string>((resolve, reject) => {
    Office.context.ui.displayDialogAsync(
      dialogUrl,
      { height: 60, width: 40, promptBeforeOpen: false },
      (asyncResult) => {
        if (asyncResult.status !== Office.AsyncResultStatus.Succeeded) {
          reject(new Error("Failed to open signing dialog"));
          return;
        }
        const dialog = asyncResult.value;

        dialog.addEventHandler(Office.EventType.DialogMessageReceived, (arg: { message: string }) => {
          dialog.close();
          try {
            const message = JSON.parse(arg.message);
            if (message.jwt) resolve(message.jwt);
            else reject(new Error(message.error || "No JWT"));
          } catch {
            reject(new Error("Invalid dialog response"));
          }
        });

        dialog.addEventHandler(Office.EventType.DialogEventReceived, () => {
          reject(new Error("Dialog was closed"));
        });
      }
    );
  });
}

Source: commands.ts#L149-L189

See the Thunderbird addon and Outlook addon pages for the full patterns.

Decryption Authentication

Decryption also requires identity verification. The same element and session patterns apply. You must provide either element or session when calling opened.decrypt(). If neither is provided, the SDK throws a DecryptionError.

Pass enableCache: true to cache the Yivi JWT across multiple decrypt() calls. This avoids forcing the user to scan a QR code for every message when decrypting a batch. See Decryption: JWT caching for details.