How to Add Digital Signatures to PDFs Using TypeScript
In this blog post, you’ll learn how to sign PDF documents using TypeScript and PSPDFKit for Web. Digital signatures, which are generated through complex mathematical processes involving cryptographic keys, provide a high level of security and ensure the authenticity and integrity of your PDF documents, making them trustworthy in various critical contexts, such as legal contracts and official records.
PSPDFKit TypeScript Digital Signature Library
PSPDFKit 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, and typed signatures. The ability to store signatures locally or remotely enhances convenience, while automated workflows can be triggered based on signature actions. PSPDFKit 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
PSPDFKit 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.
PSPDFKit’s TypeScript PDF Library
We offer a commercial TypeScript 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 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:
-
A package manager for installing the PSPDFKit library. You can use npm or Yarn. When you install Node.js, npm is installed by default.
-
TypeScript
You can install TypeScript globally by running the following command:
npm install -g typescript
Getting Started
-
Create a new folder and change your directory to it:
mkdir typescript-pspdfkit-viewer cd typescript-pspdfkit-viewer
-
Create a
package.json
file by runningnpm init --yes
. -
Create a new
tsconfig.json
configuration file in the root of your project:
tsc --init
You can customize what rules you want the TypeScript compiler to follow. This post will use the following configuration:
// tsconfig.json { "compilerOptions": { "removeComments": true, "preserveConstEnums": true, "module": "commonjs", "target": "es5", "sourceMap": true, "noImplicitAny": true, "esModuleInterop": true }, "include": ["src/**/*"] }
Check out the compiler options for more information.
Installing PSPDFKit and Configuring webpack
-
Install the PSPDFKit for Web library as a dependency:
npm install pspdfkit
-
You’ll use one of the most popular build tools, webpack, to bundle your project into a single file. It’ll help to both reduce the HTTP requests needed to load the PDF and minify your code.
Start by downloading the necessary dev
dependencies:
npm i -D webpack webpack-cli webpack-dev-server ts-loader typescript html-webpack-plugin cross-env copy-webpack-plugin clean-webpack-plugin
Here’s what’s installed:
-
webpack
— The webpack bundler. -
webpack-cli
— The command-line interface for webpack. -
webpack-dev-server
— A local server to run webpack in the browser. -
ts-loader
— A package that teaches webpack how to compile TypeScript. -
typescript
— The TypeScript compiler. -
clean-webpack-plugin
— A plugin that cleans the output directory before building. -
copy-webpack-plugin
— A plugin that copies files and directories to the output directory. -
html-webpack-plugin
— A plugin that generates an HTML file from a template. -
cross-env
— A package that allows you to set environment variables.
After the installation, you can see the dependencies in your package.json
file:
"devDependencies": { "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.3", "html-webpack-plugin": "^5.5.3", "ts-loader": "^9.4.4", "typescript": "^5.2.2", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" }
-
To configure webpack, create a
config
directory and place yourwebpack
configuration file inside it:
mkdir config && touch config/webpack.js
-
If you’re using webpack 4, use the example file. If you’re using the latest version of webpack,
^5.88.2
, use the following configuration:
// webpack.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const filesToCopy = [ // PSPDFKit files. { from: './node_modules/pspdfkit/dist/pspdfkit-lib', to: './pspdfkit-lib', }, // Application CSS. { from: './src/index.css', to: './index.css', }, // Example PDF. { from: './assets/example.pdf', to: './example.pdf', }, ]; /** * webpack main configuration object. */ const config = { entry: path.resolve(__dirname, '../src/index.ts'), mode: 'development', devtool: 'inline-source-map', output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ // All files with a `.ts` or `.tsx` extension will be handled by `ts-loader`. { test: /\.tsx?$/, loader: 'ts-loader', exclude: /node_modules/, }, ], }, plugins: [ // Automatically insert <script src="[name].js"><script> to the page. new HtmlWebpackPlugin({ template: './src/index.html', }), // Copy the WASM/ASM and CSS files to the `output.path`. new CopyWebpackPlugin({ patterns: filesToCopy }), ], optimization: { splitChunks: { cacheGroups: { // Creates a `vendor.js` bundle that contains external libraries (including `pspdfkit.js`). vendor: { test: /node_modules/, chunks: 'initial', name: 'vendor', priority: 10, enforce: true, }, }, }, }, }; module.exports = config;
The entry point of your application will be the ../src/index.ts
file, and the output will be bundled inside the dist
folder. Note that you’re copying the PDF file, PSPDFKit files, and CSS files to the output path.
You created the module
property containing the rules
array to handle the TypeScript files. So, when webpack finds a file with the .ts
or .tsx
extension, it’ll use the ts-loader
to compile it. Only then will the file be added to the dist
build.
Displaying the PDF
-
Create a folder called
assets
and add a file namedexample.pdf
to it. You can use our demo document as an example. Make sure to name the documentexample.pdf
. -
Create a folder called
src
and add a file namedindex.html
with the following code:
<!DOCTYPE html> <html> <head> <title>PSPDFKit for Web - TypeScript example</title> <link rel="stylesheet" href="index.css" /> </head> <body> <div class="container"></div> </body> </html>
This adds an empty <div>
element to where PSPDFKit will be mounted.
-
You’ll declare the height of this element in your CSS file like this:
.container {
height: 100vh;
}
-
Now, create an
index.ts
file inside thesrc
directory.
.ts
is the extension used for TypeScript files. Later, you’ll run your code through the TypeScript transpiler to output a JavaScript version of the file (.js
):
import PSPDFKit from 'pspdfkit'; function load(document: string) { console.log(`Loading ${document}...`); PSPDFKit.load({ document, container: '.container', }) .then((instance) => { console.log('PSPDFKit loaded', instance); }) .catch(console.error); } load('example.pdf');
Here, you imported the PSPDFKit
library and created a function that loads the PDF document.
Running the Project
-
Now, you need to write some scripts in the
package.json
file to start your server:
"scripts": { "build": "cross-env NODE_ENV=production webpack --config config/webpack.js", "prestart": "npm run build", "dev": "tsc", "start": "serve -l 8080 ./dist" },
-
You can run
npm start
to start the server. Navigate tohttp://localhost:8080
to see the contents of thedist
directory.
Adding a Digital Signature to a PDF Using PSPDFKit
PSPDFKit 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 outlined below.
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 generate them:
-
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.
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 that you store these files securely, as they’re essential for signing documents.
For more information on adding a digital signature to a PDF using PSPDFKit, refer to our digital signatures guide.
Signing a PDF Document Using PSPDFKit
To add a digital signature to your PDF document using PSPDFKit, 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 the
index.ts
file:
import * as forge from 'node-forge';
Step 2 — Generating the PKCS#7 Signature
PSPDFKit 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. It then 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 PSPDFKit and Signing the Document
Now, you can initialize PSPDFKit 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 rejects if an error arises:
function load(document: string) { console.log(`Loading ${document}...`); PSPDFKit.load({ document, container: '.container', }) .then((instance) => { console.log('PSPDFKit loaded', instance); // Sign the document when PSPDFKit is loaded. instance .signDocument(null, generatePKCS7) .then(() => { console.log('Document signed.'); }) .catch((error) => { console.error( 'The document could not be signed.', error, ); }); }) .catch(console.error); }
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 your reference:
import PSPDFKit from 'pspdfkit'; import * as forge from 'node-forge'; 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; } function load(document: string) { console.log(`Loading ${document}...`); PSPDFKit.load({ document, container: '.container', }) .then((instance) => { console.log('PSPDFKit loaded', instance); // Sign the document when PSPDFKit is loaded. instance .signDocument(null, generatePKCS7) .then(() => { console.log('Document signed.'); }) .catch((error) => { console.error( 'The document could not be signed.', error, ); }); }) .catch(console.error); } load('example.pdf');
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 TypeScript and PSPDFKit for Web. If you’re interested in exploring PSPDFKit further, you can request a free trial of our SDK or visit our demo page to experience the capabilities of our product firsthand.