Blog Post

How to add digital signatures to PDFs using React

Hulya Masharipov
Illustration: How to add digital signatures to PDFs using React
Information

This article was first published in December 2023 and was updated in August 2024.

In this blog post, you’ll learn how to digitally sign PDF documents using React and Nutrient Web SDK. Digital signatures have become a must for modern document handling, and they’re typically used for legal and security purposes, especially in contexts where the authenticity of a document is critical, such as in legal contracts or official documents. Throughout this tutorial, you’ll navigate the intricacies of self-signed certificate generation and the application of digital signatures to your PDFs.

Nutrient React digital signature library

Nutrient offers a range of essential capabilities for PDF signing. It simplifies the process of creating and validating digital signatures, accommodating various signature types, including hand-drawn, scanned, or typed signatures. The ability to store signatures locally or remotely enhances convenience, while automated workflows can be triggered based on signature actions. Nutrient allows for 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.

  1. 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.

  2. 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 React PDF library

We offer a commercial React.js 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 user interface (UI) that you can extend or simplify based on your unique use case.

  • A prebuilt and polished UI for an improved user experience
  • 15+ prebuilt annotation tools to enable document collaboration
  • Support for more file types with client-side PDF, MS Office, and image viewing
  • Dedicated support from engineers to speed up integration

Requirements to get started

To get started, you’ll need:

  • The latest version of Node.js.

  • A package manager compatible with npm. This guide contains usage examples for Yarn and the npm client (installed with Node.js by default).

Creating a new React project with Vite

  1. Create a new React app using Vite:

yarn create vite pspdfkit-react-example --template react
npm create vite@latest pspdfkit-react-example -- --template react
  1. Change to the created project directory:

cd pspdfkit-react-example

Adding Nutrient to your project

  1. Add the Nutrient dependency:

yarn add pspdfkit
npm install pspdfkit
  1. Copy the Nutrient Web SDK library assets to the public directory:

cp -R ./node_modules/pspdfkit/dist/pspdfkit-lib public/pspdfkit-lib

The above code will copy the pspdfkit-lib directory from within node_modules/ into the public/ directory to make it available to the SDK at runtime.

  1. Make sure your public directory contains a pspdfkit-lib directory with the Nutrient library assets.

Displaying a PDF

  1. Add the PDF document you want to display to the public directory. You can use our demo document as an example.

  2. Add a component wrapper for the Nutrient library and save it as components/PdfViewerComponent.jsx:

import { useEffect, useRef } from 'react';

export default function PdfViewerComponent(props) {
	const containerRef = useRef(null);

	useEffect(() => {
		const container = containerRef.current;
		let instance, PSPDFKit;
		(async function () {
			PSPDFKit = await import('pspdfkit');

			PSPDFKit.unload(container); // Ensure there's only one Nutrient instance.

			instance = await PSPDFKit.load({
				// Container where Nutrient should be mounted.
				container,
				// The document to open.
				document: props.document,
				// Use the public directory URL as a base URL. Nutrient will download its library assets from here.
				baseUrl: `${window.location.protocol}//${
					window.location.host
				}/${import.meta.env.BASE_URL}`,
			});
		})();

		return () => PSPDFKit && PSPDFKit.unload(container);
	}, []);

	return (
		<div
			ref={containerRef}
			style={{ width: '100%', height: '100vh' }}
		/>
	);
}
  1. Include the newly created component in App.jsx:

// src/App.jsx

import PdfViewerComponent from './components/PdfViewerComponent';

function App() {
	return (
		<div className="App" style={{ width: '100vw' }}>
			<div className="PDF-viewer">
				<PdfViewerComponent document={'document.pdf'} />
			</div>
		</div>
	);
}

export default App;
  1. Your project structure should now look like this:

pspdfkit-react-example
├── public
│   ├── pspdfkit-lib
│   └── document.pdf
├── src
│   ├── components
│   |   └── PdfViewerComponent.jsx
|   └── App.jsx
├── package.json
└── yarn.lock
  1. Start the app and run it in your default browser:

yarn dev
npm run dev

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. To do this, follow the steps in the next section.

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. Here’s how you can do this:

  1. Open your terminal in the project directory.

  2. 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.

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. Ensure you 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 below.

Step 1: Installing the Forge library

  1. Install the Forge library using npm. Open your terminal, navigate to the project directory, and run the following command:

yarn add node-forge
npm install node-forge
  1. Import the Forge library in your PdfViewerComponent file:

import 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:

import { useCallback } from 'react';

export default function PdfViewerComponent(props) {
	// Other code...
	const generatePKCS7 = useCallback(({ fileContents }) => {
		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]) => {
					const certificate = forge.pki.certificateFromPem(
						certificatePem,
					);
					const privateKey = forge.pki.privateKeyFromPem(
						privateKeyPem,
					);

					const p7 = forge.pkcs7.createSignedData();
					p7.content = new forge.util.ByteBuffer(fileContents);
					p7.addCertificate(certificate);
					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(),
							},
						],
					});

					p7.sign({ detached: true });
					const result = stringToArrayBuffer(
						forge.asn1.toDer(p7.toAsn1()).getBytes(),
					);
					resolve(result);
				})
				.catch(reject);
		});
	}, []);
	//....
}

This function fetches your certificate and private key, and then it uses Forge to create a PKCS#7 signed data structure.

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) {
	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, you should supply fileContents. This parameter is used as a callback object containing an ArrayBuffer housing a document’s content. The method returns a promise that resolves to the ArrayBuffer, or it rejects if an error arises.

useEffect(() => {
	const container = containerRef.current;
	let instance, PSPDFKit;
	(async function () {
		try {
			PSPDFKit = await import('pspdfkit');

			PSPDFKit.unload(container); // Ensure there's only one Nutrient instance.

			const instance = await PSPDFKit.load({
				// Container where Nutrient should be mounted.
				container,
				// The document to open.
				document: props.document,
				// Use the public directory URL as a base URL. Nutrient will download its library assets from here.

				baseUrl: `${window.location.protocol}//${
					window.location.host
				}/${import.meta.env.BASE_URL}`,
			});

			await instance.signDocument(null, generatePKCS7);
			console.log('document signed.');
		} catch (error) {
			console.error('The document could not be signed.', error);
		}
	})();

	return () => PSPDFKit && PSPDFKit.unload(container);
}, [generatePKCS7, props.document]);

This code initiates the signing process. Upon successful completion, you’ll see the message Document signed. in the console. If there were 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 complete code for the PdfViewerComponent:

import { useEffect, useRef, useCallback } from 'react';
import forge from 'node-forge';

export default function PdfViewerComponent(props) {
	const containerRef = useRef(null);

	const generatePKCS7 = useCallback(({ fileContents }) => {
		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]) => {
					const certificate = forge.pki.certificateFromPem(
						certificatePem,
					);
					const privateKey = forge.pki.privateKeyFromPem(
						privateKeyPem,
					);

					const p7 = forge.pkcs7.createSignedData();
					p7.content = new forge.util.ByteBuffer(fileContents);
					p7.addCertificate(certificate);
					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(),
							},
						],
					});

					p7.sign({ detached: true });
					const result = stringToArrayBuffer(
						forge.asn1.toDer(p7.toAsn1()).getBytes(),
					);
					resolve(result);
				})
				.catch(reject);
		});
	}, []);

	function stringToArrayBuffer(binaryString) {
		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;
	}

	useEffect(() => {
		const container = containerRef.current;
		let instance, PSPDFKit;
		(async function () {
			try {
				PSPDFKit = await import('pspdfkit');

				PSPDFKit.unload(container); // Ensure there's only one Nutrient instance.

				const instance = await PSPDFKit.load({
					// Container where Nutrient should be mounted.
					container,
					// The document to open.
					document: props.document,
					// Use the public directory URL as a base URL. Nutrient will download its library assets from here.
					baseUrl: `${window.location.protocol}//${
						window.location.host
					}/${import.meta.env.BASE_URL}`,
				});

				await instance.signDocument(null, generatePKCS7);
				console.log('document signed.');
			} catch (error) {
				console.error('The document could not be signed.', error);
			}
		})();

		return () => PSPDFKit && PSPDFKit.unload(container);
	}, [generatePKCS7, props.document]);

	return (
		<div
			ref={containerRef}
			style={{ width: '100%', height: '100vh' }}
		/>
	);
}
Information

We recently added support for CAdES signatures, which are advanced digital signatures. To learn more about CAdES signatures, refer to our digital signatures guide.

Conclusion

In this blog post, you learned how to add digital signatures to PDF documents using React 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.

FAQ

Here are a few frequently asked questions about adding digital signatures to PDF documents using React.

How can I integrate Nutrient into a React project?

Nutrient can be integrated into a React project by installing the Nutrient NPM package and configuring it within your React components. The tutorial provides step-by-step instructions on setting up Nutrient Web SDK in a React environment.

What are the prerequisites for adding digital signatures to PDFs in React?

To add digital signatures to PDFs in React, you need a React project set up with Nutrient Web SDK. Additionally, you’ll need access to a Nutrient license and some basic understanding of React components.

Does this tutorial use Vite instead of Create React App?

Yes, the tutorial has been updated to use Vite as the build tool instead of Create React App, providing faster development and more modern features.

What are the benefits of using Nutrient for digital signatures?

Nutrient offers a comprehensive solution for adding digital signatures to PDFs, including support for various signature formats, robust security features, and seamless integration with React applications.

How do I customize the appearance of the signature field in Nutrient?

The appearance of the signature field can be customized by modifying the Nutrient configuration and using custom styles in your React application. The tutorial includes examples of how to style the signature field to match your design requirements.

Author
Hulya Masharipov Technical Writer

Hulya is a frontend web developer and technical writer at PSPDFKit who enjoys creating responsive, scalable, and maintainable web experiences. She’s passionate about open source, web accessibility, cybersecurity privacy, and blockchain.

Explore related topics

Related products
Share post
Free trial Ready to get started?
Free trial