DEV Community

Cover image for Secure PDF Signing with Digital Signature in JavaScript PDF Viewer
Lucy Muturi for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

Secure PDF Signing with Digital Signature in JavaScript PDF Viewer

TL;DR: Developers often struggle to implement secure PDF signing in web apps due to complex cryptographic requirements and certificate handling. This guide shows how to add digital signatures in the JavaScript PDF Viewer, covering SHA256 hashing, X.509 certificates, and signature field customization for tamper-proof PDFs.

Securing digital documents is no longer optional; it’s critical for modern web applications. Sending an unsigned contract or agreement leaves it vulnerable to unauthorized edits and legal disputes. Digital signatures solve this problem by using cryptographic techniques to guarantee document integrity, verify the signer’s identity, and make tampering immediately detectable.

In this guide, you’ll learn how to add digital signatures in PDFs using Syncfusion® JavaScript PDF Viewer and .NET PDF Library. By the end, you’ll have a secure, compliant workflow ready for production.

Why digital signatures matter in PDF workflows

  • Compliance and trust: Digital signatures act as a secure seal, proving identity and locking PDFs against changes. They meet eIDAS and ESIGN standards for tamper-proof, legally binding documents.
  • Document integrity: Once signed, any modification breaks the signature, making tampering obvious. Cryptographic algorithms ensure the document remains unchanged.
  • Electronic vs digital signatures: Electronic signatures are quick but insecure, usually just an image or typed name. Digital signatures go further by using cryptography and trusted certificates to lock the document and verify identity.
  • Industry adoption: Digital signatures are widely used across industries such as legal, finance, healthcare, government, and real estate to secure critical documents, including contracts, compliance forms, and patient records.
  • Implementation challenges: Adding digital signatures isn’t trivial. Developers must handle certificates securely, ensure cross-browser compatibility, optimize cryptographic performance, and comply with PKCS#7 and X.509 standards, all while balancing security, speed, and user experience.

How to add digital signatures in JavaScript PDF Viewer

Before you begin, refer to the Syncfusion documentation for detailed instructions on creating a JavaScript PDF Viewer using CDN-hosted resources. This ensures correct setup, dependency loading, and version compatibility.

Once your environment is ready, follow the steps below.

Step 1: Initialize Syncfusion PDF Viewer

Begin by injecting the required modules, registering your license, and initializing the viewer with a sample PDF and resourceUrl.

Refer to the code below.

// client-side/app.js (excerpt)
ej.pdfviewer.PdfViewer.Inject(
    ej.pdfviewer.FormDesigner,
    ej.pdfviewer.FormFields,
    ej.pdfviewer.TextSearch,
    ej.pdfviewer.Print,
    ej.pdfviewer.Navigation,
    ej.pdfviewer.Magnification,
    ej.pdfviewer.Annotation,
    ej.pdfviewer.BookmarkView,
    ej.pdfviewer.ThumbnailView,
    ej.pdfviewer.LinkAnnotation,
    ej.pdfviewer.PageOrganizer
);

ej.base.registerLicense('YOUR_SYNCFUSION_LICENSE_KEY');

var viewer = new ej.pdfviewer.PdfViewer({
    enableToolbar: false,
    enableAnnotationToolbar: false,
    enableNavigationToolbar: false,
    enableThumbnail: false,
    zoomMode: 'FitToPage',
    documentPath: 'https://cdn.syncfusion.com/content/pdf/visible-digital-signature.pdf',
    resourceUrl: 'https://cdn.syncfusion.com/ej2/31.2.2/dist/ej2-pdfviewer-lib'
});
viewer.appendTo('#visibleSign_pdfViewer');
Enter fullscreen mode Exit fullscreen mode

At this point, the PDF Viewer is ready to display documents and accept signature inputs.

Step 2: Build signing UI

Next, provide a user interface that allows users to configure how the digital signature should be applied.

The UI supports two signing modes:

  • Create new signature: Place a visible signature at custom X/Y coordinates.
  • Use existing signature field: Apply the signature to a predefined signature field in the PDF.

Adding digital signatures using the JavaScript PDF Viewer


Adding digital signatures using the JavaScript PDF Viewer

This flexibility allows the solution to work equally well with dynamic PDFs and form-based templates.

The signing UI enables users to configure the following:

  • Display mode ( image only, signer details only, or both).
  • Signer details ( name, reason, location, and date ).
  • Image uploader for handwritten or scanned signature.
  • Signature type ( CMS or CAdES ) defines the cryptographic standard used for signing.
  • Digest algorithm (e.g, SHA256 ) ensures the integrity of the signed document.
// Tabs
function setActiveTab(index) {
    activeTab = index;
    createNewGroup.hidden = (index !== 0);
    existingFieldGroup.hidden = (index !== 1);
}
if (createNewHeader)
    createNewHeader.onclick = () => setActiveTab(0);
if (existingFieldHeader)
    existingFieldHeader.onclick = () => setActiveTab(1);

// Bounds for Create New
var xTextBox = new ej.inputs.NumericTextBox({
    value: 24,
    change: a => x = a.value
});
xTextBox.appendTo('#visibleSign_numerictextbox_x');
var yTextBox = new ej.inputs.NumericTextBox({
    value: 12,
    change: a => y = a.value
});
yTextBox.appendTo('#visibleSign_numerictextbox_y');
var widthTextBox = new ej.inputs.NumericTextBox({
    value: 200,
    change: a => width = a.value
});
widthTextBox.appendTo('#visibleSign_numerictextbox_width');
var heightTextBox = new ej.inputs.NumericTextBox({
    value: 120,
    change: a => height = a.value
});
heightTextBox.appendTo('#visibleSign_numerictextbox_height');

// Display mode and image toggle
var displayModes = ['Image only', 'With signer details', 'Signer details only'];
var displayModeDropdown = new ej.dropdowns.DropDownList({
    dataSource: displayModes,
    value: displayModes[1]
});
displayModeDropdown.appendTo('#visibleSign_dropdown_displayMode');

var signatureImageCheckbox = new ej.buttons.CheckBox({
    label: 'Signature Image',
    checked: true,
    change: args => handleSignImagesVisibility(args.checked)
});
signatureImageCheckbox.appendTo('#visibleSign_checkbox_signatureImage');

// Existing field selector
var existingFieldDropdown = new ej.dropdowns.DropDownList({
    dataSource: ['Signature Field 1'],
    value: 'Signature Field 1',
    placeholder: 'Select Existing Field'
});
existingFieldDropdown.appendTo('#visibleSign_dropdown_existingField');
Enter fullscreen mode Exit fullscreen mode

Step 3: Build and dispatch the signing payload (Client-to-Server)

After the user finishes configuring the signature, the client prepares a request payload and send it to the server for signing:

1. Extract PDF data

Use the saveAsBlob() to retrieve the current PDF content. The PDF is converted into a Base64-encoded string for transport.

2. Build JSON payload

This JSON payload includes:

  • Signature Type (CMS or CAdES)
  • Digest Algorithm (e.g., SHA256)
  • Display Mode (image only, signer details only, or both)
  • Signer Details (name, reason, location, and date)
  • Optional Image (Base64 format)
  • Signature Bounds (X, Y, width, height) if creating a new signature For existing fields, set isSignatureField = true.

3. Send to server

The payload is dispatched via an HTTP POST request to the signing API endpoint. This triggers the actual digital signature process, in which the server performs cryptographic operations using the provided certificate.

// Build payload from UI
var getRequestBody = function (pdfData) {
    var body = {
        pdfdata: pdfData,
        signatureType: (signatureTypeDropdown.value).toUpperCase(),
        displayMode: (displayModeDropdown.value).toUpperCase(),
        digestAlgorithm: (digestAlgorithmDropdown.value).toUpperCase()
    };
    if (signatureImageCheckbox.checked && (displayModeDropdown.value !== 'Signer details only')) {
        body.imagedata = imageUrls[selectedImageIndex]; // base64 image
    }
    if (descriptionCheckboxes.signer.checked)
        body.signerName = descriptionTextboxes.signer.value;
    if (descriptionCheckboxes.reason.checked)
        body.reason = descriptionTextboxes.reason.value;
    if (descriptionCheckboxes.location.checked)
        body.location = descriptionTextboxes.location.value;
    if (descriptionCheckboxes.date.checked)
        body.date = formatDate(descriptionTextboxes.date.value);

    if (activeTab === 0) {
        body.isSignatureField = false;
        body.signatureBounds = JSON.stringify({
            x: xTextBox.value,
            y: yTextBox.value,
            width: widthTextBox.value,
            height: heightTextBox.value
        });
    } else {
        body.isSignatureField = true;
    }
    return body;
};

// Send request and reload signed PDF
var signDocument = function () {
    viewer.saveAsBlob().then(function (blob) {
        var fr = new FileReader();
        fr.readAsDataURL(blob);
        fr.onload = function (e) {
            var req = new XMLHttpRequest();
            req.open('POST', 'https://localhost:44334/api/PdfViewer/AddVisibleSignature', true);
            req.setRequestHeader('Content-type', 'application/json charset=UTF-8');
            req.onload = function () {
                if (req.status === 200) {
                    viewer.load(req.responseText, null); // data:application/pdf;base64,...
                    viewer.fileName = fileName;
                    viewer.downloadFileName = fileName;
                    signDocumentButton.disabled = true;
                }
            };
            req.send(JSON.stringify(getRequestBody(e.target.result)));
        };
    });
};
Enter fullscreen mode Exit fullscreen mode

Step 4: Server-side signing

Once the client sends the signing request, the server-side Web API performs these operations:

1. Decode incoming payload

The API receives a JSON object containing:

  • Base64-encoded PDF data
  • Optional signature image
  • Signature Metadata (type, digest algorithm, signer details, display mode)

The PDF data is decoded and converted into a binary stream for processing.

2. Load the PDF document

Using the Syncfusion .NET PDF Library, the server loads the PDF into memory for modification.

3. Load X.509 certificate (PFX)

The signing process requires a private key and identity. This is provided by an X.509 certificate stored in a PFX file (protected by a password).

The PFX file contains:

  • Private key: Used to generate a digital signature.
  • Public certificate: Used for validation by recipients.

4. Determine signature placement

Based on the isSignatureField flag:

  • Existing field: The signature is applied to a predefined signature field in the PDF.
  • Create new: A new visible signature is placed at custom coordinates provided by the client.

5. Enterprise options

For advanced scenarios, Syncfusion supports:

  • OS certificate stores (Windows Certificate Store)
  • CRL/OCSP for revocation checks
  • Hardware Security Modules (HSM) for high-assurance key storage
// Load the PDF
byte[] documentBytes = Convert.FromBase64String(pdfdataString);
using PdfLoadedDocument loadedDocument = new PdfLoadedDocument(documentBytes);

// Existing field (first field) if the user chose an existing field
PdfLoadedSignatureField formField = null;
if (loadedDocument.Form?.Fields?.Count > 0)
{
    formField = loadedDocument.Form.Fields[0] as PdfLoadedSignatureField;
} 
// First page
PdfPageBase loadedPage = loadedDocument.Pages[0];

// Certificate (update file name/password as needed)
PdfCertificate pdfCertificate = new PdfCertificate(GetDocumentPath("localhost.pfx"), "Syncfusion@123");

// Optional signature image
PdfImage image = null;
if (jsonObject.ContainsKey("imagedata"))
{
    string imageData = jsonObject["imagedata"]?.ToString() ?? string.Empty;
    string[] imgSplit = imageData.Split(
        new string[] {
            "data:image/png;base64,", "data:image/jpeg;base64,", "data:image/jpg;base64,"
        }, StringSplitOptions.None
    );

    if (imgSplit.Length > 1 && !string.IsNullOrEmpty(imgSplit[1]))
    {
        byte[] imageBytes = Convert.FromBase64String(imgSplit[1]);
        MemoryStream imageStream = new MemoryStream(imageBytes);
        image = new PdfBitmap(imageStream);
    }
} 
// Signature text
PdfStandardFont font = new PdfStandardFont(PdfFontFamily.Helvetica, 8);
PdfSignature signature;
string descriptionText = "";
string signerName = "";
string reason = "";
string locationInfo = "";
DateTime signingDate = DateTime.Now;

if (jsonObject.ContainsKey("signerName"))
{
    signerName = jsonObject["signerName"]?.ToString() ?? "";
    if (!string.IsNullOrEmpty(signerName))
        descriptionText += "Digitally signed by " + signerName + "\n";
}
if (jsonObject.ContainsKey("reason"))
{
    reason = jsonObject["reason"]?.ToString() ?? "";
    if (!string.IsNullOrEmpty(reason))
        descriptionText += "Reason: " + reason + "\n";
}
if (jsonObject.ContainsKey("location"))
{
    locationInfo = jsonObject["location"]?.ToString() ?? "";
    if (!string.IsNullOrEmpty(locationInfo))
        descriptionText += "Location: " + locationInfo + "\n";
}
if (jsonObject.ContainsKey("date"))
{
    string dateStr = jsonObject["date"]?.ToString() ?? "";
    if (!string.IsNullOrWhiteSpace(dateStr))
    {
        descriptionText += "Date: " + dateStr;

        var formats = new[] { "MM-dd-yyyy", "MM-dd-yy" }; // support both if needed
        if (!DateTime.TryParseExact( dateStr, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var givenDate))
        {
            return BadRequest($"Invalid date format: {dateStr}. Expected MM-dd-yyyy.");
        }
        signingDate = new DateTime( givenDate.Year, givenDate.Month, givenDate.Day, signingDate.Hour, signingDate.Minute, signingDate.Second);
    }
} 
string displayMode = jsonObject.ContainsKey("displayMode") ? jsonObject["displayMode"]?.ToString() ?? "" : "";

bool isSignatureField =
    jsonObject.ContainsKey("isSignatureField") &&
    bool.TryParse(jsonObject["isSignatureField"]?.ToString(), out bool v) &&
    v;
Enter fullscreen mode Exit fullscreen mode

Step 5: Apply signature and cryptographic settings

After the server has loaded the PDF and certificate, it creates the digital signature:

1. Signature placement

  • Existing field: Apply the signature to the predefined field.
  • New signature: Create a visible signature at the specified coordinates.

2. Appearance customization

  • Add signature image if provided.
  • Render signer details based on the selected display mode.

3. Cryptographic configuration

  • Standard: Choose CMS or CAdES.
  • Digest algorithm: Apply SHA256 or other secure hash algorithms.
  • Mark the signature as certificated, ensuring tamper detection and compliance.

The code example below ensures the PDF is cryptographically sealed, making any post-signing changes immediately detectable.

if (isSignatureField)
{
    // Sign existing field
    loadedDocument.FlattenAnnotations();
    signature = new PdfSignature(loadedDocument, loadedPage, pdfCertificate, "Signature", formField, signingDate);

    if (!string.Equals(displayMode, "SIGNER DETAILS ONLY", StringComparison.OrdinalIgnoreCase) && image != null && formField != null)
    {
        float boundsWidth = formField.Bounds.Width * 0.57f;
        if (string.Equals(displayMode, "IMAGE ONLY", StringComparison.OrdinalIgnoreCase) ||
            descriptionText.Length == 0)
            boundsWidth = formField.Bounds.Width;

        float boundsHeight = formField.Bounds.Height;
        Bounds imgBounds = GetVisibleSignImageBounds(boundsHeight, boundsWidth, image.Height, image.Width);

        signature.Appearance.Normal.Graphics.DrawImage(
            image,
            imgBounds.X, imgBounds.Y,
            imgBounds.Width, imgBounds.Height
        );
    }
}
else
{
    // New signature with given bounds
    signature = new PdfSignature(loadedDocument, loadedPage, pdfCertificate, "Signature", signingDate);

    Bounds signatureBounds = JsonSerializer.Deserialize<Bounds>(
        jsonObject["signatureBounds"]?.ToString() ?? "{}",
        new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
    );

    signature.Bounds = new Rectangle(signatureBounds.X, signatureBounds.Y, signatureBounds.Width, signatureBounds.Height);

    if (!string.Equals(displayMode, "SIGNER DETAILS ONLY", StringComparison.OrdinalIgnoreCase) && image != null)
    {
        float boundsWidth = signatureBounds.Width * 0.57f;
        if (string.Equals(displayMode, "IMAGE ONLY", StringComparison.OrdinalIgnoreCase) ||
            descriptionText.Length == 0)
            boundsWidth = signatureBounds.Width;

        float boundsHeight = signatureBounds.Height;
        Bounds imgBounds = GetVisibleSignImageBounds(boundsHeight, boundsWidth, image.Height, image.Width);

        signature.Appearance.Normal.Graphics.DrawImage(
            image,
            imgBounds.X, imgBounds.Y,
            imgBounds.Width, imgBounds.Height
        );
    }
}

// Draw signer details text (if requested)
if (!string.Equals(displayMode, "IMAGE ONLY", StringComparison.OrdinalIgnoreCase) && descriptionText.Length > 0)
{
    PdfStringFormat format = new PdfStringFormat
    {
        Alignment = PdfTextAlignment.Left,
        LineAlignment = PdfVerticalAlignment.Middle
    };

    if (string.Equals(displayMode, "WITH SIGNER DETAILS", StringComparison.OrdinalIgnoreCase))
    {
        signature.Appearance.Normal.Graphics.DrawString(
            descriptionText,
            font,
            PdfBrushes.Black,
            new RectangleF(signature.Bounds.Width * 0.6f, 0, signature.Bounds.Width * 0.4f, signature.Bounds.Height),
            format
        );
    }
    else
    {
        signature.Appearance.Normal.Graphics.DrawString(
            descriptionText,
            font,
            PdfBrushes.Black,
            new RectangleF(0, 0, signature.Bounds.Width, signature.Bounds.Height),
            format
        );
    }
}

// Signature settings
if (jsonObject.ContainsKey("signatureType") || jsonObject.ContainsKey("digestAlgorithm"))
{
    PdfSignatureSettings settings = signature.Settings;

    if (jsonObject.ContainsKey("signatureType"))
    {
        string sigType = jsonObject["signatureType"]?.ToString() ?? "";
        if (string.Equals(sigType, "CADES", StringComparison.OrdinalIgnoreCase))
            settings.CryptographicStandard = CryptographicStandard.CADES;
        else if (string.Equals(sigType, "CMS", StringComparison.OrdinalIgnoreCase))
            settings.CryptographicStandard = CryptographicStandard.CMS;
    }
    if (jsonObject.ContainsKey("digestAlgorithm"))
    {
        string algo = jsonObject["digestAlgorithm"]?.ToString() ?? "";
        settings.DigestAlgorithm = (DigestAlgorithm)Enum.Parse(typeof(DigestAlgorithm), algo, ignoreCase: true);
    }
}

signature.Certificated = true;
Enter fullscreen mode Exit fullscreen mode

Step 6: Save and reload the signed PDF

Finally, the server saves the signed document and returns it.

1. Save document

Convert the signed PDF into a Base64-encoded string.

2. Return to the client

Send the signed PDF back to the client as a data URL.

3. Reload in the viewer

The client instantly loads the signed document into the PDF Viewer, allowing:

  • Immediate visual confirmation of the signature.
  • Validation status display.
  • Option to download the signed PDF.
using var ms = new MemoryStream();
doc.Save(ms);
var result = "data:application/pdf;base64," + Convert.ToBase64String(ms.ToArray());
return Content(result);
Enter fullscreen mode Exit fullscreen mode
// client-side/app.js (on success)
req.onload = function () {
    if (req.status === 200) {
        viewer.load(req.responseText, null); // signed PDF shown instantly
        viewer.fileName = fileName;
        viewer.downloadFileName = fileName;
        signDocumentButton.disabled = true;
    }
};
Enter fullscreen mode Exit fullscreen mode

Creating a new signature field

In the Create New tab, you can add a visible digital signature to any location on a PDF page. The signature is positioned by specifying its X and Y coordinates, along with its width and height, giving you precise control over placement.

Key Steps

1. Define Signature Bounds

The client specifies the signature’s exact position and size by sending the X, Y, width, and height values in the request payload. These coordinates tell the server where and how large the signature should appear on the PDF page.

2. Customize the Signature Appearance

You can tailor how the signature is displayed to meet branding or legal requirements:

  • Display mode: Choose one of the following options:
    • Image Only,
    • Signer Details Only
    • Image and Singer Details.
  • Signature image: Upload a scanned or handwritten signature image encoded in Base64 format.
  • Signer details: Include relevant information such as the signer’s name, signing reason, location, and date to provide legal context and audit clarity.

3. Apply cryptographic settings

To ensure the integrity and authenticity of the document, the server applies cryptographic protections using the provided certificate. You can select the appropriate signing standard, CMS or CAdES, along with a secure digest algorithm such as SHA‑256 to create a tamper‑evident signature.

Creating a new digital signature field in the JavaScript PDF Viewer


Creating a new digital signature field in the JavaScript PDF Viewer

Signing in an existing field

If the PDF already contains a predefined signature field, the signing process is streamlined by targeting that field directly rather than creating a new one.

Key Steps

1. Select the Existing Field

The client identifies the target signature field by including its field name in the request payload and setting isSignatureField = true. This instructs the server to use the existing field rather than creating a new signature area.

2. Customize the Signature Display

As with newly created fields, you can control how the signature appears within the predefined field:

  • Display mode: Choose how the signature is rendered:
    • Image Only
    • Signer Details Only
    • Image and Signer Details
  • Signature image Provide a scanned or handwritten signature image in Base64 format.
  • Signer details Include contextual information such as the signer’s name, signing reason, location, and signing date to support auditability and legal compliance.

3. Server‑Side Signing

The server locates the specified signature field within the PDF and applies the digital signature. It then embeds the required cryptographic settings, using the supplied certificate, selected signing standard, and digest algorithm, to ensure document integrity, authenticity, and regulatory compliance.

Signing an existing field in the JavaScript PDF Viewer


Signing an existing field in the JavaScript PDF Viewer

GitHub reference

To explore the full implementation with a working sample on GitHub. Clone the repo, run the code, and see digital signatures in action.

Conclusion

Thank you for reading! Implementing digital signatures in PDF workflows is more than just adding a visual mark; It’s about ensuring data integrity, authenticity, and compliance. By leveraging cryptographic standards like CMS or CAdES and secure algorithms such as SHA256, you create tamper-proof documents that meet global regulations like eIDAS and ESIGN.

Syncfusion’s JavaScript PDF Viewer and .NET PDF library simplify this process by offering flexible integration, enterprise-grade security, and seamless user experience.

Whether you’re building a legal contract system, financial approval workflow, or healthcare compliance solution, digital signatures are essential for trust and security. With Syncfusion JavaScript PDF Viewer and .NET PDF library, you can implement these features confidently and efficiently.

Ready to secure your PDFs? Check out our live demo of Digital Signature in JavaScript PDF Viewer and explore how easy it is to implement secure signing in your web applications.

If you’re a Syncfusion user, you can download the setup from the license and downloads page. Otherwise, you can download a free 30-day trial.

You can also contact us through our support forum, support portal, or feedback portal for queries. We are always happy to assist you!

Related Blogs

This article was originally published at Syncfusion.com

Top comments (0)