TL;DR: Developers often struggle with assigning eSignature permissions in multi-user PDF workflows. This guide shows how to implement user-based eSignatures using Syncfusion® ASP.NET Core PDF Viewer, enabling secure, role-specific signing with audit trails and form field control.
Introduction
Managing eSignatures in multi-user environments can be tricky. Developers need a secure, scalable way to assign signing permissions without compromising document integrity. Electronic signatures (eSignatures) have become essential for streamlining document workflows and ensuring secure, compliant approval processes. While basic eSign functionality is valuable, implementing user-based eSigning elevates document management to an enterprise level by providing granular control over who can sign what, when, and where.
In this guide, we’ll walk through how to implement user-based eSignatures using Syncfusion® ASP.NET Core PDF Viewer, solving a common pain point in enterprise document workflows. Unlike generic eSign solutions, our implementation assigns specific form fields to designated users, ensures proper validation, and maintains a secure audit trail throughout the signing process.
Why User-based eSigning Is essential
Traditional eSign solutions often fall short in complex business scenarios where multiple stakeholders need to sign different sections of the same document. User-based eSigning addresses these challenges by providing:
Granular permissions and control
- Role-Based Assignments: Assign signature fields to specific users (employees, supervisors, managers, etc.)
- Workflow Enforcement: Ensure documents follow proper approval hierarchies
- Field-Level Security: Users can only access and modify their designated form fields
Enhanced security and compliance
- User Authentication: Only authorized users can sign their assigned fields
- Digital Integrity: Maintains document authenticity with tamper-evident signatures
- Audit Trail: Complete tracking of who signed what and when
- Legal Compliance: Meets requirements for ESIGN Act, eIDAS, and other regulations
Streamlined business processes
- Automated Routing: Documents automatically flow between stakeholders
- Reduced Errors: Prevents confusion about signing responsibilities
- Faster Turnaround: Eliminates bottlenecks from unclear signing processes
- Better User Experience: Clear visual indicators show each user’s responsibilities
Tools and setup: What you’ll need
To build this user-based eSign system, you’ll need the following components:
Core technology stack
- NET Core (version 6.0 or later)
- Syncfusion® PDF Viewer for ASP.NET Core package
- Entity Framework Core (for user management) (optional)
Syncfusion® PDF Viewer: Your eSigning foundation
The Syncfusion® ASP.NET Core PDF Viewer is an enterprise-grade component that serves as the backbone of our user-based eSigning solution. Here’s what makes it perfect for this implementation:
Key Capabilities:
- Interactive Form Design: Drag-and-drop form field creation with real-time preview
- Multi-Format Support: Works with PDFs containing existing form fields or blank documents
- Advanced Tools: Built-in signature pad, text boxes, checkboxes, and custom form elements
- Cross-Browser Compatibility: Consistent experience across modern browsers
- Mobile Responsiveness: Touch-friendly interface for tablet and smartphone signing
The PDF Viewer’s flexibility allows developers to create sophisticated workflows where each user sees only their designated form fields, complete with color-coding and validation rules.
Additional requirements
- Designer PDF Template: A base PDF document that will serve as your form template
- User Authentication System: For managing user identities and permissions (optional – In this blog, we used the basic user management. You can integrate the authentication system based on the requirement)
- Database: To store user details, form field assignments, and signing status (optional – In this blog, we used the application-level storage. You can integrate the database based on the requirement)
Implementation overview
Our user-based eSign system follows a five-phase workflow:
- User-Based Form Design: Assign form fields to specific users during the design phase
- Form Designer View: Interactive design interface for creating and configuring form fields
- Loading Designed Forms: Transform designed forms into signable documents
- User-Specific Signing: Allow users to sign only their assigned fields with validation
- Document Finalization: Flatten, secure, and distribute the completed document
Let’s dive into each phase with detailed implementation examples.
Phase 1: User-based form design
The foundation of our system is the user management structure that defines who can sign what. Here’s how we set up user-based field assignments:
User configuration structure
// Define users with unique identifiers and properties
var userDetails = [
{
Name: 'Andrew Fuller',
Eimg: 'profile2',
Id: "ff0000", // Unique color identifier for visual distinction
Mail: "andrew@mycompany.com",
Role: "Manager",
fieldIds: [] // Will store assigned form field references
},
{
Name: 'Anne Dodsworth',
Eimg: 'profile1',
Id: "00ff00",
Mail: "anne@mycompany.com",
Role: "Employee",
fieldIds: []
}
];
User selection interface
The system provides an intuitive dropdown interface for selecting users during form design:
<!-- User Selection Dropdown -->
<ejs-dropdownlist id="e-pv-e-sign-employees" dataSource="@userDetails" index="0" popupHeight="200px" select="userChange">
<e-dropdownlist-fields text="Name" value="Id"></e-dropdownlist-fields>
</ejs-dropdownlist>
Color-coded field assignment
Each user is assigned a unique color that visually distinguishes their form fields:
/* User-specific color coding */
.andrew-fields {
background-color: rgba(255, 0, 0, 0.2); /* Red for Andrew */
border: 2px solid #ff0000;
}
.anne-fields {
background-color: rgba(0, 255, 0, 0.2); /* Green for Anne */
border: 2px solid #00ff00;
}
Phase 2: Form designer view – interactive field creation
The Form Designer View provides a comprehensive interface for creating and assigning form fields. This phase combines drag-and-drop functionality with user assignment logic.
Document loading and mode initialization
function documentLoad() {
pdfViewer = document.getElementById('pdfviewer').ej2_instances[0];
if (showFinishSigningButton) {
// Switch to signing mode
pdfViewer.designerMode = false;
updateUserFormField();
pdfViewer.updateViewerContainer();
} else {
// Enable form design mode
pdfViewer.designerMode = true;
initializeFormFieldPalette();
}
}
function initializeFormFieldPalette() {
// Initialize draggable form fields
initializeDraggable(document.getElementById('signature-btn'), 'SignatureField');
initializeDraggable(document.getElementById('textbox-btn'), 'Textbox');
initializeDraggable(document.getElementById('password-btn'), 'Password');
initializeDraggable(document.getElementById('checkbox-btn'), 'CheckBox');
initializeDraggable(document.getElementById('radio-btn'), 'RadioButton');
initializeDraggable(document.getElementById('dropdown-btn'), 'DropDown');
initializeDraggable(document.getElementById('list-btn'), 'ListBox');
initializeDraggable(document.getElementById('initial-btn'), 'InitialField');
}
Drag-and-drop field creation
The system supports intuitive drag-and-drop field creation with automatic user assignment:
function initializeDraggable(element, fieldType) {
// Enable drag functionality
element.draggable = true;
element.addEventListener('dragstart', function(e) {
e.dataTransfer.setData('fieldType', fieldType);
e.dataTransfer.setData('currentUser', getCurrentUser());
});
}
// Handle field drop events
pdfViewer.addEventListener('formFieldAdd', addFormField);
function addFormField(args) {
var currentUser = getCurrentUser();
var userColor = getUserColor(currentUser);
// Assign user-specific metadata to the form field
if (currentUser === 'andrew@mycompany.com') {
pdfViewer.formDesigner.updateFormField(
pdfViewer.retrieveFormFields()[pdfViewer.formFieldCollections.length - 1],
{
customData: { author: 'andrew' },
backgroundColor: userColor,
isReadOnly: false
}
);
} else if (currentUser === 'anne@mycompany.com') {
pdfViewer.formDesigner.updateFormField(
pdfViewer.retrieveFormFields()[pdfViewer.formFieldCollections.length - 1],
{
customData: { author: 'anne' },
backgroundColor: userColor,
isReadOnly: false
}
);
}
// Add field to user's collection
var currentUserDetails = userDetails.filter(user => user.Mail === currentUser)[0];
var currentFormField = pdfViewer.formFieldCollections.filter(field => field.id === args.field.id)[0];
currentUserDetails.fieldIds.push(currentFormField);
// Visual feedback
showFieldAssignmentNotification(currentUser, args.field.fieldType);
}
Advanced field configuration
Beyond basic field creation, the system supports advanced configuration options:
function configureAdvancedFieldProperties(fieldId, userConfig) {
var field = pdfViewer.formFieldCollections.find(f => f.id === fieldId);
if (field) {
pdfViewer.formDesigner.updateFormField(field, {
// User-specific styling
backgroundColor: userConfig.backgroundColor,
borderColor: userConfig.borderColor,
fontColor: userConfig.fontColor,
// Permissions
isReadOnly: false, // Will be set during signing phase
customData: { //Set more user-specific details as JSON data
author: userConfig.author,
}
});
}
}
Reference designed PDF Form fields:
Phase 3: Loading designed forms in the viewer
Once form design is complete, the system transitions from design mode to signing mode. This phase involves saving the designed form and reloading it with user-specific permission.
function signFile() {
// Save the designed form as a blob
pdfViewer.saveAsBlob().then(function (value) {
data = value;
var reader = new FileReader();
reader.readAsDataURL(data);
reader.onload = () => {
base64data = reader.result;
// Reload the form in signing mode
pdfViewer.load(base64data, null);
pdfViewer.width = "100%";
pdfViewer.updateViewerContainer();
// Configure UI for signing mode
transitionToSigningMode();
};
});
}
function transitionToSigningMode() {
// Update UI state
showSignButton = false;
showFinishSigningButton = true;
// Hide design tools
document.getElementById("sidebarObj").style.display = "none";
document.getElementById("pdf-div").style.width = "100%";
document.getElementById("pdf-div").style.marginLeft = "0";
// Update toolbar for signing operations
updateToolbar();
}
User-specific field visibility
When transitioning to signing mode, the system configures field visibility based on user permissions:
function updateUserFormField() {
// Get all form fields from the PDF viewer
const formFieldCollections = pdfViewer.formFieldCollections;
// Separate fields based on assigned user (author)
const otherFormFieldDetails = formFieldCollections.filter(f => f.customData.author === 'anne');
const currentFormFieldDetails = formFieldCollections.filter(f => f.customData.author === 'andrew');
// If the current user is Andrew
if (currentUser === 'andrew@mycompany.com') {
otherFormFieldDetails.forEach(field => {
const isFilled = field.value !== "";
const fieldElement = document.getElementById(field.id + '_content_html_element');
const currentField = formFieldCollections.find(f => f.id === field.id);
if (isFilled) {
// Mark Anne's completed fields as finished and read-only
pdfViewer.formDesigner.updateFormField(field, { backgroundColor: finishedBackground });
pdfViewer.formDesignerModule.updateFormField(field, { isReadOnly: true });
// Mark Andrew's fields as read-only with his background color
currentFormFieldDetails.forEach(currentField => {
pdfViewer.formDesigner.updateFormField(currentField, { backgroundColor: andrewBackground });
pdfViewer.formDesignerModule.updateFormField(currentField, { isReadOnly: true });
});
} else {
// If Anne's fields are not filled, highlight Andrew's fields
currentFormFieldDetails.forEach(currentField => {
pdfViewer.formDesigner.updateFormField(currentField, { backgroundColor: andrewBackground });
});
}
// Hide Anne's unfilled fields from Andrew's view
if (fieldElement && currentField) {
const shouldHide = currentField.type !== 'DropDown'
? !currentField.value
: currentField.value.length !== 0;
if (shouldHide) {
pdfViewer.formDesignerModule.updateFormField(currentField, { visibility: 'hidden' });
}
}
});
} else {
// For Anne or other users, validate Andrew's fields first
validation(currentFormFieldDetails);
if (!state) {
// If validation fails, lock Andrew's fields and enable Anne's
currentFormFieldDetails.forEach(field => {
pdfViewer.formDesigner.updateFormField(field, { backgroundColor: finishedBackground });
pdfViewer.formDesignerModule.updateFormField(field, { isReadOnly: true });
otherFormFieldDetails.forEach(otherField => {
pdfViewer.formDesigner.updateFormField(otherField, { backgroundColor: anneBackground });
pdfViewer.formDesignerModule.updateFormField(otherField, { isReadOnly: false });
// Make Anne's fields visible
const otherUserField = document.getElementById(otherField.id + '_content_html_element');
if (otherUserField) {
pdfViewer.formDesignerModule.updateFormField(otherField, { visibility: 'visible' });
}
});
});
}
}
}
Phase 4: User-specific signing process
This is the core phase where users interact with their assigned form fields. The system enforces strict validation and provides clear visual feedback.
function userChange(args) {
// Update the current user based on the selected item's email
currentUser = args.itemData.Mail;
// Get the user image element
const userImage = document.getElementById('user-img');
// Set border color based on selected user
if (currentUser === 'andrew@mycompany.com') {
userImage.style.borderColor = 'red';
} else {
userImage.style.borderColor = 'green';
}
// Update form fields based on the selected user
updateUserFormField();
// If change is prevented (e.g., due to validation), revert the border color and cancel the selection
if (preventChange) {
userImage.style.borderColor = 'red'; // Optional: could be based on previous user
args.cancel = true;
}
}
Real-time validation and feedback
This function validates all required form fields, including checkboxes, radio buttons, dropdowns, and text inputs, before allowing users to proceed.
It provides immediate feedback by highlighting missing fields and displaying a clear error message dialog.
function validation(forms) {
let errorMessage = "Required Field(s): ";
let allFieldsValid = true;
let radioGroupName = "";
let radioSelected = false;
// Iterate through each form field to validate
forms.forEach(form => {
let fieldNameToAdd = "";
if (form.isRequired) {
switch (form.type.toString()) {
case "Checkbox":
// If the checkbox is required but not checked
if (!form.isChecked) {
fieldNameToAdd = form.name;
allFieldsValid = false;
}
break;
case "RadioButton":
// Track if any radio button in the group is selected
if (!radioSelected) {
radioGroupName = form.name;
if (form.isSelected) radioSelected = true;
}
break;
case "DropdownList":
// If dropdown is required but no value selected
if (!form.value || form.value.length === 0) {
fieldNameToAdd = form.name;
allFieldsValid = false;
}
break;
default:
// For text fields or others, check if value is empty
if (!form.value || (typeof form.newValue === 'string' && form.newValue === "")) {
fieldNameToAdd = form.name;
allFieldsValid = false;
}
break;
}
// Append field name to error message if validation failed
if (fieldNameToAdd) {
errorMessage += errorMessage === "Required Field(s): " ? fieldNameToAdd : `, ${fieldNameToAdd}`;
}
}
});
// If no radio button was selected in a required group
if (!radioSelected && radioGroupName) {
errorMessage += errorMessage === "Required Field(s): " ? radioGroupName : `, ${radioGroupName}`;
allFieldsValid = false;
}
// Show error dialog if any field is invalid
if (!allFieldsValid) {
state = true;
preventChange = true;
dialogObj.content = errorMessage;
dialogObj.show();
} else {
state = false;
preventChange = false;
}
}
Reference screenshot after filling the form fields based on the user.
Phase 5: Document finalization and security
The final phase handles document completion, validation, flattening, and secure distribution.
Comprehensive validation before finalization
This method finalizes the signing process by marking all form fields as completed and sending the document to the server for flattening.
It then downloads the secured PDF and prevents further edits, ensuring the document is ready for distribution or archival.
function finishSigning() {
// Step 1: Mark all form fields as completed with a background color
for (const formField of pdfViewer.formFieldCollections) {
pdfViewer?.formDesignerModule.updateFormField(formField, {
backgroundColor: finishedBackground
});
}
// Step 2: Define the API endpoint for flattening the document
const url = '/api/Home/FlattenDownload';
// Step 3: Save the current PDF as a Blob and convert it to Base64
pdfViewer.saveAsBlob()
.then(blob => convertBlobToBase64(blob))
.then(base64String => {
// Step 4: Prepare and send the POST request with the Base64 string
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
const requestData = JSON.stringify({ base64String });
xhr.onload = () => {
if (xhr.status === 200) {
// Step 5: Extract the Base64 PDF from the response
const responseBase64 = xhr.responseText.split('base64,')[1];
if (responseBase64) {
// Step 6: Convert Base64 to Blob and create a download link
const blob = createBlobFromBase64(responseBase64, 'application/pdf');
const blobUrl = URL.createObjectURL(blob);
// Step 7: Trigger the download and reload the PDF in read-only mode
downloadDocument(blobUrl);
pdfViewer.load(xhr.responseText, null);
// Step 8: Disable further signing actions
document.getElementById('e-pv-e-sign-finishSigning').disabled = true;
document.getElementById('e-pv-e-sign-employees').ej2_instances[0].enabled = false;
} else {
console.error('Invalid base64 response.');
}
} else {
console.error('Download failed:', xhr.statusText);
}
};
xhr.onerror = () => {
console.error('An error occurred during the download:', xhr.statusText);
};
xhr.send(requestData);
})
.catch(error => {
console.error('Error saving Blob:', error);
});
}
Secure the document by flattening using the PDF library on the controller side
This API endpoint receives a base64-encoded PDF, flattens form fields and annotations to make them non-editable, and returns the updated document.
It ensures the secure finalization of signed documents before download or archival.
public IActionResult FlattenDownload([FromBody] Dictionary<string, string> jsonObject)
{
try
{
string documentBase = "";
// Step 1: Extract base64 string from the request payload
if (jsonObject != null && jsonObject.ContainsKey("base64String"))
{
documentBase = jsonObject["base64String"];
}
// Step 2: Remove data URI prefix if present
string base64String = documentBase.Contains("data:application/pdf;base64,")
? documentBase.Split(new[] { "data:application/pdf;base64," }, StringSplitOptions.None)[1]
: documentBase;
// Step 3: Convert base64 string to byte array
byte[] byteArray = Convert.FromBase64String(base64String);
// Step 4: Load the PDF document from a byte array
PdfLoadedDocument loadedDocument = new PdfLoadedDocument(byteArray);
// Step 5: Flatten form fields and annotations if present
if (loadedDocument.Form != null)
{
loadedDocument.FlattenAnnotations(); // Optional: flatten annotations
loadedDocument.Form.Flatten = true; // Make form fields non-editable
}
// Step 6: Save the updated PDF to a memory stream
using (MemoryStream stream = new MemoryStream())
{
loadedDocument.Save(stream);
stream.Position = 0;
loadedDocument.Close(true);
// Step 7: Convert the stream back to base64
string updatedDocumentBase = Convert.ToBase64String(stream.ToArray());
string responseBase64 = "data:application/pdf;base64," + updatedDocumentBase;
// Step 8: Return the flattened PDF as a base64 string
return Content(responseBase64);
}
}
catch (Exception ex)
{
// Return error message if processing fails
return BadRequest($"Error processing PDF: {ex.Message}");
}
}
GitHub reference
For more details, refer to the GitHub demo.
Scalability recommendations
- Database optimization: Use proper indexing for user and document queries
- File storage: Consider cloud storage (Azure Blob, AWS S3) for large documents
- Caching: Implement Redis for session management and temporary data
- Load balancing: Use application load balancers for high-traffic scenarios
- CDN integration: Serve static assets through a CDN for better performance
Conclusion
Thank you for reading! Implementing user-based eSignatures doesn’t have to be complex. With Syncfusion’s ASP.NET Core PDF Viewer, you can build secure, role-driven workflows that scale. This comprehensive user-based eSign PDF form provides a robust foundation for enterprise document workflows. The implementation ensures:
- Security: Granular user permissions and comprehensive audit trails
- Usability: Intuitive interface with clear visual feedback
- Compliance: Meets legal requirements for electronic signatures
- Scalability: Designed to handle multiple users and complex workflows
- Flexibility: Easily customizable for specific business requirements
The modular architecture allows organizations to extend the system with additional features, such as integration with existing HR systems, advanced approval workflows, or industry-specific compliance requirements.
By implementing this solution, organizations can significantly streamline their document approval processes while maintaining the highest standards of security and legal compliance. The user-based approach ensures that the right people sign the right fields at the right time, eliminating confusion and reducing processing time.
Whether you’re building a simple approval system or a complex multi-stakeholder document workflow, this implementation provides the foundation and flexibility to meet your organization’s unique requirements.
Additional Resources
- Syncfusion PDF Viewer Documentation
- NET Core Security Best Practices
- Electronic Signature Legal Compliance Guide
- PDF/A Standards for Long-term Preservation
Ready to build your own? Explore the new features of Essential Studio® from the license and downloads page or take advantage of our 30-day free trial to transform your data narratives. If you’ve any questions or need support, contact us through our support forum, support portal, or feedback portal. Happy coding!
Related Blogs
- Deferred PDF Signing in .NET: How to Do It Right
- 6 Effective Ways to Merge PDF Files Using C#
- 5 Effective Ways to Navigate PDF Pages in C# Using .NET PDF Library
- 7 Ways to Compress PDF Files in C#, VB.NET
This article was originally published at Syncfusion.com.
Top comments (0)