DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Essential Security Measures for PDF Documents
  • Building a PDF Viewer From Scratch
  • Template-Based PDF Document Generation in JavaScript
  • Document Generation API: How to Automate Personalized Document Creation at Scale

Trending

  • Jakarta EE 12: Entering the Data Age of Enterprise Java
  • The 7 Pillars of Meeting Design: Transforming Expensive Conversations into Decision Assets
  • Reactive Ops to Autonomous Infrastructure: How Agentic AI Is Redefining Modern DevOps
  • Why Your RAG Pipeline Will Fail Without an MCP Server
  1. DZone
  2. Coding
  3. JavaScript
  4. Handling Password-Protected PDFs in JavaScript

Handling Password-Protected PDFs in JavaScript

Handling password-protected PDFs in JavaScript isn’t as hard as it seems. This guide shows how to use OSS libraries to open secured PDFs in the browser.

By 
John Pagley user avatar
John Pagley
·
Aug. 06, 25 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
4.9K Views

Join the DZone community and get the full member experience.

Join For Free

PDF is one of the simplest formats for sharing documents. They are portable and can provide basic access control through password protection. In this post, we will discuss one of many ways to unlock and open password-protected PDF documents in JavaScript.

This post uses PDF.js and client-side JavaScript tools built into modern browsers to:

  1. Read a PDF file from a user’s device.
  2. Prompt for passwords only when the PDF is password-protected.
  3. Display feedback for failed attempts to unlock a PDF file.
  4. Render pages of the decrypted PDF using the browser’s Canvas API.

Start a New JavaScript Project

As a first step, let’s set up scaffolding for a vanilla JavaScript application using Vite. Run the below command in your terminal to create a new vanilla JavaScript web app named pdf-password and install its dependencies.

npm create vite@latest -- --template vanilla pdf-password && cd pdf-password && npm install

Next, install PDF.js as a project dependency:

npm install pdfjs-dist

Then, open the newly created pdf-password folder in your preferred code editor to begin building the PDF viewer.

Create HTML Elements to Handle User Input and PDF Rendering

Replace the contents of the project’s index.html file with the following.

HTML
 
<!-- pdf-password/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Locked PDF Viewer</title>
  </head>
  <body>
    <form class="pdf-form">
      <label for="pdf-form__input" class="pdf-form__label"
        >View a PDF file in your browser</label
      >
      <input type="file" name="pdf-file" id="" class="pdf-form__input" />
    </form>

    <div class="password-form-backdrop">
      <form class="password-form">
        <label for="" class="password-form__label"
          >The PDF is password-protected. Please enter its password.</label
        >
        <p class="password-incorrect">Incorrect password. Please try again.</p>
        <input
          type="password"
          name="password"
          class="password-input"
          placeholder="PDF Password"
          autocomplete=""
          autofocus
        />
        <button type="submit" class="password-submit">Unlock</button>
      </form>
    </div>

    <canvas class="pdf-canvas"></canvas>

    <script type="module" src="/src/main.js"></script>
  </body>
</html>


The first form in the above markup handles file uploads from the user’s device, while the second form collects a PDF’s password. The canvas element will be used to render the PDF’s pages.

Since we’re aiming for a visually pleasing PDF upload form (even though it’s just a demo), let’s replace the contents of the project’s src/style.css file with the following style rules:

CSS
 
/* pdf-password/src/style.css */
*,
::before,
::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body,
input,
input::placeholder,
button,
.pdf-form__input::file-selector-button {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

body {
  background-color: rgb(239, 246, 253);
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 1em;
  max-width: 100%;
}

form {
  background-color: white;
  border-radius: 1em;
  padding: 4em 2em;
  flex-basis: 400px;
  max-width: 100%;
}

label {
  font-size: 1.5rem;
  display: inline-block;
  margin-bottom: 12px;
}

.pdf-form__input::file-selector-button {
  border: 2px solid rgba(0, 0, 0, 0.1);
  border-radius: 0.5em;
  padding: 0.75em;
  font-size: 1rem;
  background-color: transparent;
  cursor: pointer;
}

.password-form-backdrop {
  display: none;
  position: fixed;
  background-color: rgba(0, 0, 0, 0.5);
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  justify-content: center;
  align-items: center;
}

.password-incorrect {
  color: rgb(220, 20, 60);
  display: none;
}

.password-input,
.password-submit {
  border: 2px solid rgba(0, 0, 0, 0.1);
  border-radius: 0.5em;
  padding: 0.75em;
  font-size: 1rem;
  width: 100%;
  margin-bottom: 20px;
}

.password-submit {
  border: none;
  background-color: dodgerblue;
  color: white;
  cursor: pointer;
}

.pdf-canvas {
  max-width: 100%;
  margin: 0 auto;
  display: none;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}


CSS
 
/* pdf-password/src/style.css */
*,
::before,
::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body,
input,
input::placeholder,
button,
.pdf-form__input::file-selector-button {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

body {
  background-color: rgb(239, 246, 253);
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 1em;
  max-width: 100%;
}

form {
  background-color: white;
  border-radius: 1em;
  padding: 4em 2em;
  flex-basis: 400px;
  max-width: 100%;
}

label {
  font-size: 1.5rem;
  display: inline-block;
  margin-bottom: 12px;
}

.pdf-form__input::file-selector-button {
  border: 2px solid rgba(0, 0, 0, 0.1);
  border-radius: 0.5em;
  padding: 0.75em;
  font-size: 1rem;
  background-color: transparent;
  cursor: pointer;
}

.password-form-backdrop {
  display: none;
  position: fixed;
  background-color: rgba(0, 0, 0, 0.5);
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  justify-content: center;
  align-items: center;
}

.password-incorrect {
  color: rgb(220, 20, 60);
  display: none;
}

.password-input,
.password-submit {
  border: 2px solid rgba(0, 0, 0, 0.1);
  border-radius: 0.5em;
  padding: 0.75em;
  font-size: 1rem;
  width: 100%;
  margin-bottom: 20px;
}

.password-submit {
  border: none;
  background-color: dodgerblue;
  color: white;
  cursor: pointer;
}

.pdf-canvas {
  max-width: 100%;
  margin: 0 auto;
  display: none;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}


Import the Project’s Dependencies

Delete the contents of the project’s src/main.js file, then import and configure PDF.js and the project’s stylesheet into the src/main.js script as shown below:

JavaScript
 
// pdf-password/src/main.js
import './style.css';

// Import pdfJs
import {
  GlobalWorkerOptions,
  getDocument,
  PasswordResponses,
} from 'pdfjs-dist';

// Setup pdfJs' worker from the package's node_modules folder
GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.mjs',
  import.meta.url
).toString();

At this point, the project’s index page should look similar to the following image if you run npm run dev -- --open from the root folder of the project.


Display the PDF’s Contents

Copy the following code into the src/main.js file. It attaches an event listener to the file input field of the pdf-form element defined in the index.html file. The event listener detects when a user selects a PDF and its handler attempts to display the document’s contents on the screen.

JavaScript
 
// Display an uploaded PDF file
document.querySelector('.pdf-form__input')
.addEventListener('change', viewPDF);


Now let’s define the viewPDF event handler, which is passed as the second argument of the addEventListener method. Copy the following code into the src/main.js file. Check out the comments in the function definition for some insight into what each statement and expression does.


function viewPDF(e) {
  // Get the File object of the uploaded PDF.
  const file = e.target.files[0];

  // Continue only if the user has uploaded a PDF document
  if (file.type !== 'application/pdf') {
    alert(`Error: ${file.name} is not a PDF file`);
    return;
  }

  // Read the contents of the PDF file from the user's device
  const reader = new FileReader();
  reader.readAsArrayBuffer(file);

  // Handle error(s) encountered while reading the contents of the PDF file
  reader.onerror = () => {
    alert(`Unable to read ${file.name} to an ArrayBuffer`);
    console.error(reader.error);
  };

  // Wait till FileReader has read all contents of the PDF file before proceeding
  reader.onload = async () => {
    // Transform the contents of the PDF file to a generic byte array
    const bytes = new Uint8Array(reader.result);

    // Using PDF.js, start loading the PDF contents from the above byte array
    const loadingTask = getDocument(bytes);

    // Prompt for a password only if PDF.js detects password protection while loading the document
    loadingTask.onPassword = handlePDFPassword;

    // Complete the process of loading the PDF document
    const pdfDocument = await loadingTask.promise;

    // Hide the PDF upload form since we don't need it anymore
    document.querySelector('.pdf-form').style.display = 'none';

    renderPage(pdfDocument);
  };
}


Prompt for a Password and Unlock Password-Protected PDFs

The handlePDFPassword function set as the loadingTask’s onPassword event handler is undefined at this point. Let’s define it by adding the below function to the src/main.js file.

JavaScript
 
function handlePDFPassword(setPassword, reason) {
  const passwordForm = document.querySelector('.password-form-backdrop').style;
  const passwordIncorrect = document.querySelector('.password-incorrect').style;
   
   // Prompt for a password if PDF.js needs the file’s password to proceed
  if (reason === PasswordResponses.NEED_PASSWORD) {
    passwordForm.display = 'flex';
    passwordIncorrect.display = 'none';

    document.querySelector('.password-form').addEventListener('submit', (e) => {
      e.preventDefault();
      setPassword(document.querySelector('.password-input').value);
       
      // Hide password prompt after the correct password is submitted
      passwordForm.display = 'none';
      passwordIncorrect.display = 'none';
    });
  }

   // Display incorrect password error message if the entered password doesn’t unlock the PDF file
  if (reason === PasswordResponses.INCORRECT_PASSWORD) {
    passwordForm.display = 'flex';
    passwordIncorrect.display = 'block';
  }
}


handlePDFPassword gets called only if PDF.js detects password protection when loading a PDF document. Users trying to view non-password-protected PDFs won’t be prompted for a password.

Render the Unlocked PDF’s Pages

Finally, copy the following function definition to the src/main.js file. Like its name suggests, the renderPage function renders a page of the loaded PDF onto the web page.

JavaScript
 
async function renderPage(pdfDocument) {
  // Load the first page of the document.
  const page = await pdfDocument.getPage(1);

  // Use the page's dimensions (in pixels) to set the
  // dimensions of the canvas on which the page will be rendered
  const viewport = page.getViewport({ scale: 1 });
  const canvas = document.querySelector('.pdf-canvas');
  const canvasContext = canvas.getContext('2d');

  canvas.style.display = 'block';
  canvas.height = viewport.height;
  canvas.width = viewport.width;

  // Render the PDF page on the site
  const renderTask = page.render({
    canvasContext,
    viewport,
  });
  await renderTask.promise;
}


Try It Out!

Open the app in your browser (by running npm run dev -- --open), click the Choose file button, and select a password-protected PDF file.


The app will prompt for the PDF’s password using the pop-up form shown below before displaying the PDF’s contents.


Go Further

The renderPage function defined above renders only the first page of the PDF document. See https://mozilla.github.io/pdf.js/examples for guidance on adding pagination and/or better error handling to the app.

Document JavaScript PDF

Published at DZone with permission of John Pagley. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Essential Security Measures for PDF Documents
  • Building a PDF Viewer From Scratch
  • Template-Based PDF Document Generation in JavaScript
  • Document Generation API: How to Automate Personalized Document Creation at Scale

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook