How to add digital signatures to PDFs using Angular
This article was first published in November 2023 and was updated in October 2024.
In this blog post, you’ll learn how to sign PDF documents with Angular and Nutrient Web SDK, using digital signatures to make sure your documents stay safe and reliable, especially when they’re particularly important — like legal contracts and official records.
Nutrient Angular digital signature library
Nutrient offers a range of essential capabilities for PDF signing. It streamlines the process of creating and validating electronic signatures, which encompass a range of signature types, such as hand-drawn, scanned, and typed signatures. While it’s important to note that electronic signature are, in essence, distinct from traditional digital signatures, their visual representations can serve as the appearance for a digital signature. An example illustrating this concept can be found in our demo.
The ability to store signatures locally or remotely enhances convenience, while automated workflows can be triggered based on signature actions. Nutrient allows for user interface (UI) customization, ensuring a seamless user experience, and it supports client-side signing, eliminating the need for a dedicated server. Additionally, it can be extended to include forms, annotations, and other PDF-related functionalities, making it a versatile tool for various document tasks.
Signature support
Nutrient offers two types of signatures: electronic signatures and digital signatures.
-
Electronic signatures allow users to create signatures with ink drawings, bitmap images, or text. Our Electronic Signatures component provides a user-friendly interface, supporting draw, image, and type signature modes, and it enables signature storage for reuse.
-
Digital signatures, on the other hand, are signed with certificates, offering reliable proof of a document’s origin and protection against unauthorized changes. While distinct, these signature types can be used together for a comprehensive signing solution.
Nutrient’s Angular PDF library
We offer a commercial Angular PDF viewer library that can easily be integrated into your web application. It comes with 30+ features that let you view, annotate, edit, and sign documents directly in your browser. Out of the box, it has a polished and flexible UI that you can extend or simplify based on your unique use case.
- A prebuilt UI — Save time with a well-documented list of APIs when customizing the UI to meet your exact requirements.
- Annotation tools — Draw, circle, highlight, comment, and add notes to documents with 15+ prebuilt annotation tools.
- Multiple file types — Support client-side viewing of PDFs, MS Office documents, and image files.
- 30+ features — Easily add features like PDF editing, digital signatures, form filling, real-time document collaboration, and more.
- Dedicated support — Deploy faster by working 1-on-1 with our developers.
Requirements to get started
To get started, you’ll need:
-
A package manager for installing the Angular command-line interface (CLI) and importing packages. You can use npm or Yarn.
When you install Node.js,
npm
is installed by default.
Setup
Go to your terminal and install the Angular CLI. This will help you get up and running quickly with Angular:
npm install -g @angular/cli
Now, check the version of Angular:
ng version
Creating a new Angular project
Now you’ll see how to integrate Nutrient into your Angular project.
First, create a new Angular project for Nutrient integration:
ng new pspdfkit-web-example-angular
This will ask some configuration questions. Choose No
for routing and CSS
for the stylesheet.
Now, change your directory to this project:
cd pspdfkit-web-example-angular
Adding Nutrient
Install pspdfkit
as a dependency with npm
or yarn
:
npm install pspdfkit
yarn add pspdfkit
Now, add the following to your angular.json
file. Angular will copy the Nutrient library assets to the assets
directory before running your app:
"assets": [ "src/favicon.ico", "src/assets", { "glob": "**/*", "input": "./node_modules/pspdfkit/dist/pspdfkit-lib/", "output": "./assets/pspdfkit-lib/" } ]
Displaying the PDF
In newer Angular versions, the assets
directory may not be created automatically when setting up a new project. This change reflects updates in Angular’s project structure. If your project is missing the assets
folder, you can easily create it under the src
directory by running:
mkdir src/assets
After creating the folder, add your PDF document to the src/assets
directory. Feel free to use our demo document as a sample.
-
Replace the contents of
app.component.html
with:
<div class="app"> <div class="toolbar"> <img class="logo" src="/favicon.ico" height="32" /> PSPDFKit Angular Application </div> <!-- We'll mount the PSPDFKit UI to this element. --> <div class="pspdfkit-container"></div> </div>
-
Replace the contents of
app.component.ts
with:
import { Component } from "@angular/core"; import PSPDFKit from "pspdfkit"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["app.component.css"], standalone: true, }) export class AppComponent { title = "PSPDFKit for Web Angular Example"; ngAfterViewInit(): void { PSPDFKit.load({ // Use the assets directory URL as a base URL. Nutrient will download its library assets from here. baseUrl: location.protocol + "//" + location.host + "/assets/", document: "/assets/example.pdf", container: ".pspdfkit-container", licenseKey: "YOUR_LICENSE_KEY_GOES_HERE", // Optional license key. }).then((instance) => { // For the sake of this demo, store the Nutrient Web SDK instance // on the global object so that you can open the dev tools and // play with the Nutrient API. (<any>window).instance = instance; }); } }
The license key is optional; however, you may see a watermark on your PDF files without a key. To get a key, contact Sales.
If you try to run your project, you may get an error stating the mounting container has no height. To fix this issue, add the following styles to the src/app/app.component.css
file:
:host { height: 100%; } .app { position: fixed; width: 100%; height: 100%; top: 0; right: 0; bottom: 0; left: 0; } .toolbar { position: relative; display: flex; align-items: center; height: 64px; width: 100%; padding: 0 24px; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); font-family: sans-serif; font-size: 20px; font-weight: 500; color: rgba(0, 0, 0, 0.8); } .logo { margin-right: 20px; } .pspdfkit-container { height: calc(100% - 64px); }
You can access the project on GitHub.
-
Start the app and open it in your default browser:
npm start
yarn start
Adding a digital signature to a PDF using Nutrient
Nutrient requires an X.509 certificate and a private key pair for adding a digital signature to a PDF document. The steps below outline how to do this.
Step 1 — Generating a self-signed certificate and private key
You can generate a self-signed certificate and private key using OpenSSL, which is a widely used tool for working with certificates and keys.
-
Open your terminal in the project directory.
-
Run the following OpenSSL command to generate a self-signed certificate and private key:
openssl req -x509 -sha256 -nodes -newkey rsa:2048 -keyout private-key.pem -out cert.pem
-
-x509
— Tells OpenSSL to create a self-signed certificate. -
-sha256
— Specifies the hash function to use for the certificate. -
-nodes
— Prevents encryption of the private key. You can remove this option for production keys if encryption is desired. -
-newkey rsa:2048
— Generates a new RSA private key with a key size of 2,048 bits. -
-keyout private-key.pem
— Specifies the name of the private key file. -
-out cert.pem
— Specifies the name of the certificate file.
Follow the prompts to provide information for the certificate, such as the Common Name (CN), organization, and location. These details will be embedded in the certificate.
Self-signed certificates should only be used for development and testing purposes. In production environments, it’s crucial to obtain a certificate from a trusted Certificate Authority (CA) to ensure the security and authenticity of your application.
Step 2 — Verifying your certificate
After generating the certificate and private key, you can verify if the certificate is correctly PEM-encoded using the following command:
openssl pkcs7 -noout -text -print_certs -in cert.pem
This command will display certificate details and shouldn’t produce any errors. It confirms that “cert.pem” is a PEM-encoded X.509 certificate.
Alternatively, if you want to verify DER-encoded certificates, you can use the following command:
openssl pkcs7 -inform der -noout -text -print_certs -in cert.pem
This command checks if “cert.pem” is a DER-encoded PKCS#7 certificate.
With these steps, you’ll have generated the required X.509 certificate and private key pair for adding digital signatures to your PDF documents. Be certain to store these files securely, as they’re essential for signing documents.
For more information on adding a digital signature to a PDF using Nutrient, refer to our digital signatures guide.
Signing a PDF document using Nutrient
To add a digital signature to your PDF document using Nutrient, follow the steps outlined below.
Step 1 — Installing the Forge library
-
Install the Forge library using
npm
. Open your terminal, navigate to the project directory, and run the following command:
npm install node-forge npm i --save-dev @types/node-forge
-
Import the Forge library in your
app.component.ts
file:
import * as forge from 'node-forge';
Step 2 — Generating the PKCS#7 signature
Nutrient utilizes the cryptographic Distinguished Encoding Rules (DER) PKCS#7 format for digital signatures. You’ll need to create a valid PKCS#7 signature containing your certificate and other relevant information.
Define a function, generatePKCS7
, to generate the digital signature for your PDF. This function will perform the necessary cryptographic operations:
function generatePKCS7({ fileContents, }: { fileContents: ArrayBuffer | null, }): Promise<ArrayBuffer> { // Fetch the certificate and private key. const certificatePromise = fetch('cert.pem').then((response) => response.text(), ); const privateKeyPromise = fetch('private-key.pem').then((response) => response.text(), ); return new Promise((resolve, reject) => { Promise.all([certificatePromise, privateKeyPromise]) .then(([certificatePem, privateKeyPem]) => { // Parse the certificate and private key using Forge.js. const certificate = forge.pki.certificateFromPem( certificatePem, ); const privateKey = forge.pki.privateKeyFromPem( privateKeyPem, ); // Create a PKCS7 signature. const p7 = forge.pkcs7.createSignedData(); if (!fileContents) { throw new Error('No file contents provided.'); } const buffer = forge.util.createBuffer(fileContents); p7.content = buffer.getBytes(); p7.addCertificate(certificate); // Add the signer information. p7.addSigner({ key: privateKey, certificate: certificate, digestAlgorithm: forge.pki.oids['sha256'], authenticatedAttributes: [ { type: forge.pki.oids['contentType'], value: forge.pki.oids['data'], }, { type: forge.pki.oids['messageDigest'], }, { type: forge.pki.oids['signingTime'], value: new Date().toISOString(), }, ], }); // Sign the data. p7.sign({ detached: true }); // Convert the result to an `ArrayBuffer`. const result = stringToArrayBuffer( forge.asn1.toDer(p7.toAsn1()).getBytes(), ); resolve(result); }) .catch(reject); }); }
This function fetches your certificate and private key, and it then uses Forge to create a PKCS#7 signed data structure.
In professional settings, it’s essential to prioritize the security of your private key. While this example simplifies the process by fetching the private key from a file, in practice, you should consider more robust security measures. This includes storing the private key in a secure hardware device and potentially encrypting it. Also, please be aware that the `.pem` file generated with OpenSSL in this example isn’t encrypted, and in production, you should ensure the private key’s protection accordingly.
Step 3 — Converting a string to an array buffer
You’ll need a utility function, stringToArrayBuffer
, to convert a binary string into an ArrayBuffer
:
function stringToArrayBuffer(binaryString: string): ArrayBuffer { const buffer = new ArrayBuffer(binaryString.length); let bufferView = new Uint8Array(buffer); for (let i = 0, len = binaryString.length; i < len; i++) { bufferView[i] = binaryString.charCodeAt(i); } return buffer; }
Step 4 — Initializing Nutrient and signing the document
Now, you can initialize Nutrient and invoke the PSPDFKit.Instance#signDocument
method. This method takes two arguments.
-
Argument 1 — You can use this argument to fine-tune the signing process by providing essential data, such as certificates and private keys. If you don’t have specific signing requirements, you can pass
null
. -
Argument 2 — In the second argument, supply
fileContents
. This parameter is used as a callback object containing anArrayBuffer
housing the document’s content. The method returns a promise that resolves to theArrayBuffer
or is rejected if an error arises:
export class AppComponent { title = 'PSPDFKit for Web Angular Example'; ngAfterViewInit(): void { PSPDFKit.load({ // Use the assets directory URL as a base URL. Nutrient will download its library assets from here. baseUrl: location.protocol + '//' + location.host + '/assets/', document: '/assets/example.pdf', container: '.pspdfkit-container', }) .then((instance) => { // Store the Nutrient instance in a global variable for later use. (window as any).instance = instance; // Sign the document when Nutrient is loaded. instance.signDocument(null, generatePKCS7) .then(() => { console.log('Document signed.'); }) .catch((error) => { console.error('The document could not be signed.', error); }); }) .catch((error) => { console.error(error.message); }); } }
This code initiates the signing process. Upon successful completion, you’ll see the message ‘Document signed.’ in the console. In case of errors during the signing process, an error message will be logged.
After successfully building and implementing this code, the signing process will run automatically, and the document will reload with the freshly added digital signature.
Here’s the full code for app.component.ts
for your reference:
import { Component } from '@angular/core'; import PSPDFKit from 'pspdfkit'; import * as forge from 'node-forge'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], standalone: true, }) export class AppComponent { title = 'PSPDFKit for Web Angular Example'; ngAfterViewInit(): void { PSPDFKit.load({ // Use the assets directory URL as a base URL. Nutrient will download its library assets from here. baseUrl: location.protocol + '//' + location.host + '/assets/', document: '/assets/example.pdf', container: '.pspdfkit-container', }) .then((instance) => { // Store the Nutrient instance in a global variable for later use. (window as any).instance = instance; // Sign the document when Nutrient is loaded. instance.signDocument(null, generatePKCS7) .then(() => { console.log('Document signed.'); }) .catch((error) => { console.error('The document could not be signed.', error); }); }) .catch((error) => { console.error(error.message); }); } } function generatePKCS7({ fileContents, }: { fileContents: ArrayBuffer | null; }): Promise<ArrayBuffer> { // Fetch the certificate and private key. const certificatePromise = fetch('cert.pem').then((response) => response.text() ); const privateKeyPromise = fetch('private-key.pem').then((response) => response.text() ); return new Promise((resolve, reject) => { Promise.all([certificatePromise, privateKeyPromise]) .then(([certificatePem, privateKeyPem]) => { // Parse the certificate and private key using Forge.js. const certificate = forge.pki.certificateFromPem(certificatePem); const privateKey = forge.pki.privateKeyFromPem(privateKeyPem); // Create a PKCS7 signature. const p7 = forge.pkcs7.createSignedData(); if (!fileContents) { throw new Error('No file contents provided.'); } const buffer = forge.util.createBuffer(fileContents); p7.content = buffer.getBytes(); p7.addCertificate(certificate); // Add the signer information. p7.addSigner({ key: privateKey, certificate: certificate, digestAlgorithm: forge.pki.oids['sha256'], authenticatedAttributes: [ { type: forge.pki.oids['contentType'], value: forge.pki.oids['data'], }, { type: forge.pki.oids['messageDigest'], }, { type: forge.pki.oids['signingTime'], value: new Date().toISOString(), }, ], }); // Sign the data. p7.sign({ detached: true }); // Convert the result to an `ArrayBuffer`. const result = stringToArrayBuffer( forge.asn1.toDer(p7.toAsn1()).getBytes() ); resolve(result); }) .catch(reject); }); } function stringToArrayBuffer(binaryString: string): ArrayBuffer { const buffer = new ArrayBuffer(binaryString.length); let bufferView = new Uint8Array(buffer); for (let i = 0, len = binaryString.length; i < len; i++) { bufferView[i] = binaryString.charCodeAt(i); } return buffer; }
We recently added support for CAdES-based signatures, which are advanced digital signatures. To learn more about them, check out our guide.
Conclusion
In this blog post, you learned how to add digital signatures to PDF documents using Angular and Nutrient Web SDK. If you’re interested in exploring Nutrient further, you can request a free trial of our SDK or visit our demo page to experience the capabilities of our product firsthand.