Skip to content

AutoFirma Module for Laravel/Filament

Index

  1. Introduction
  2. Requirements
  3. Installation
  4. Configuration
  5. Basic Usage
  6. Components
  7. API
  8. Customization
  9. Troubleshooting
  10. FAQ

Introduction

The AutoFirma module provides full integration with the Spanish Government's AutoFirma digital signature client for Laravel applications using Filament. It allows users to digitally sign PDF documents using their digital certificates.

Key Features

✅ Digital signing of PDF documents (PAdES format)
✅ Support for Spanish certificates (DNIe, FNMT, etc.)
✅ Digital signature validation
✅ Download signed documents
✅ Full signature audit trail
✅ Integrated interface with Filament v3
✅ Multi-language support (ES/EN)

Requirements

Server Requirements

  • PHP 8.3 or higher
  • Laravel 11.x or higher
  • Filament 3.x
  • PHP Extensions:
    • ext-openssl
    • ext-json
    • fileinfo

Client Requirements

  • AutoFirma installed on the user's computer (firmaelectronica.gob.es)
  • Valid digital certificate (DNIe, FNMT-RCM, etc.)
  • Supported browsers:
    • Chrome/Edge (recommended)
    • Firefox
    • Safari 11+
    • Brave

Installation

1. Install dependencies

bash
composer require phpseclib/phpseclib:^3.0
  1. Install the module:
bash
composer require e2tmk/autofirma-module

or

bash
php artisan mm:require-module autofirma
  1. Publish assets and config:
bash
php artisan vendor:publish --tag=autofirma-config
php artisan vendor:publish --tag=autofirma-assets
php artisan vendor:publish --tag=autofirma-translations
  1. Run migrations:
bash
php artisan migrate
  1. Register the plugin in Filament (Optional):
php
use Modules\AutoFirma\Filament\Plugin\AutoFirmaPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            AutoFirmaPlugin::make(),
        ]);
}

Configuration

Config File

config/autofirma.php:

php
return [
    'enabled' => env('AUTOFIRMA_ENABLED', true),

    'formats' => [
        'pdf' => 'PAdES',
        'xml' => 'XAdES',
        'default' => 'CAdES',
    ],

    'storage' => [
        'disk' => env('AUTOFIRMA_STORAGE_DISK', 'local'),
        'signed_documents_path' => 'autofirma/signed',
        'max_file_size' => 50 * 1024 * 1024,
    ],

    'validation' => [
        'check_revocation' => env('AUTOFIRMA_CHECK_REVOCATION', false),
        'trusted_cas' => [
            'AC FNMT Usuarios',
            'AC Administración Pública',
        ],
    ],
];

Environment Variables

env
AUTOFIRMA_ENABLED=true
AUTOFIRMA_STORAGE_DISK=local
AUTOFIRMA_CHECK_REVOCATION=false

Basic Usage

1. Add actions to table

php
use Modules\AutoFirma\Filament\Actions\SignPdfAction;
use Modules\AutoFirma\Filament\Actions\ValidateSignatureAction;
use Modules\AutoFirma\Filament\Actions\DownloadSignedAction;

public function table(Table $table): Table
{
    return $table
        ->columns([
            // your columns
        ])
        ->actions([
            SignPdfAction::make()
                ->documentField('file_path')
                ->documentType('Document') // Model
                ->signatureFormat('PAdES'),

            ValidateSignatureAction::make()
                ->documentType('Document'), // Model

            DownloadSignedAction::make()
                ->documentType('Document'), // Model
        ]);
}

2. Make model signable

Migration:

php
Schema::table('documents', function (Blueprint $table) {
    $table->string('status')->default('draft');
    $table->string('signed_file_path')->nullable();
    $table->timestamp('signed_at')->nullable();
    $table->unsignedBigInteger('signed_by')->nullable();
    $table->json('signature_metadata')->nullable();
});

Model:

php
class Document extends Model
{
    protected $fillable = [
        'title', 'file_path', 'status',
        'signed_file_path', 'signed_at',
        'signed_by', 'signature_metadata',
    ];

    protected function casts(): array {
        return [
            'signed_at' => 'datetime',
            'signature_metadata' => 'array',
        ];
    }

    public function markAsSigned(array $signatureData = []): bool
    {
        return $this->update([
            'status' => 'signed',
            'signed_at' => now(),
            'signed_by' => auth()->id(),
            'signature_metadata' => $signatureData,
            'signed_file_path' => $signatureData['signed_document_path'] ?? null,
        ]);
    }
}

Components

SignPdfAction

php
SignPdfAction::make()
    ->documentField('file')
    ->documentType('Document') // Model
    ->signatureFormat('PAdES')
    ->modalHeading('Sign Document')
    ->modalDescription('Use your digital certificate')
    //TODO - available in future updates
    ->extraParams([
        ...
        'signatureVisible' => 'true',
        'signaturePage' => '1',
        'layer2Text' => 'Digitally signed',
        ...
    ]);

ValidateSignatureAction

php
ValidateSignatureAction::make()
    ->documentType('Document') // Model
    ->label('Verify')
    ->icon('heroicon-o-shield-check');

DownloadSignedAction

php
DownloadSignedAction::make()
    ->documentType('Document') // Model
    ->downloadOriginalIfNotSigned(true);

API

POST /api/autofirma/signatures

json
{
    "document_id": "1",
    "document_type": "Document", // Model
    "signature": "base64_of_signed_pdf",
    "certificate": "base64_of_certificate",
    "metadata": {
        "filename": "contract.pdf"
    }
}

Response

json
{
    "success": true,
    "message": "Digital signature saved successfully",
    "data": {
        "id": 123,
        "signed_at": "2025-01-26T10:30:00Z",
        "signer_name": "John Smith",
        "signer_nif": "12345678A",
        "format": "PAdES"
    }
}

Other endpoints

  • GET /api/autofirma/signatures?document_id=123&document_type=Contract
  • GET /api/autofirma/signatures/{id}/download
  • GET /api/autofirma/signatures/{id}/verify

JavaScript API

js
const autoFirma = new AutoFirmaWrapper({
    timeout: 60000,
    formats: {
        pdf: 'PAdES',
        xml: 'XAdES',
        default: 'CAdES'
    }
});

const result = await autoFirma.signDocument(documentBase64, {
    format: 'PAdES',
    algorithm: 'SHA256withRSA'
});

const isAvailable = await autoFirma.checkAvailability();

Backend Service

php
use Modules\AutoFirma\Services\AutoFirmaService;

public function __construct(
    private AutoFirmaService $autoFirmaService
) {}

$signature = $this->autoFirmaService->processSignature(
    documentId: '1',
    documentType: 'Document', // Model
    signatureBase64: $signatureData,
    certificateBase64: $certificateData,
    metadata: ['user_id' => auth()->id()]
);

Customization

Visible signature

js
buildSignatureParams(config) {
    const params = {};
    if (config.format === 'PAdES') {
        'signatureVisible' => 'true',
        'signaturePage' => '1',
        'signaturePositionOnPageLowerLeftX' => '50',
        'signaturePositionOnPageLowerLeftY' => '50',
        'signaturePositionOnPageUpperRightX' => '200',
        'signaturePositionOnPageUpperRightY' => '100',
        'layer2Text' => 'Signed by: $$SUBJECTCN$$\nDate: $$SIGNDATE=dd/MM/yyyy$$',
        'layer2FontFamily' => '2',
        'layer2FontSize' => '12',
        'layer2FontColor' => 'black',
    }
    if (config.extraParams) {
        Object.assign(params, config.extraParams);
    }
    return this.serializeParams(params);
}

Add logo to signature

js
'signatureRubricImage' => base64_encode(file_get_contents('logo.png')),

Custom translations

bash
cp vendor/e2tmk/autofirma/resources/lang/es/autofirma.php \
resources/lang/vendor/autofirma/es/autofirma.php

Troubleshooting

Check installation

bash
Windows: C:\Program Files\AutoFirma\AutoFirma.exe
macOS: /Applications/AutoFirma.app
Linux: /usr/bin/AutoFirma

Service not responding

Check WebSocket ports: 63117, 64917, 55437

Error: "Failed to store digital signature"

php
ini_set('post_max_size', '50M');
ini_set('upload_max_filesize', '50M');
ini_set('memory_limit', '512M');
bash
chmod -R 775 storage/app/autofirma

Error: "Malformed UTF-8 characters"

php
return response()->streamDownload(
    function () use ($content) {
        echo $content;
    },
    'signed_document.pdf',
    ['Content-Type' => 'application/pdf']
);

Signature not visible

js
...
'signatureVisible' => 'true',
'signaturePage' => '1',
'layer2Text' => 'Digitally signed',
...

Error: "AutoFirma not found"

bash
Windows: Check in Programs and Features  
macOS: Check in Applications  
Linux: Run `which autofirma`

Browser issues

  • Avoid incognito mode
  • Clear cache
  • Check if browser allows afirma:// protocol

Error: Too many function arguments

bash
ls -la public/vendor/autofirma/js/autoscript.js
wc -l public/vendor/autofirma/js/autoscript.js

CORS

php
// Good
$url = '/storage/' . $document;

// Bad
$url = 'http://domain.com/storage/' . $document;

Security Considerations

  • Always use HTTPS
  • Validate certificates server-side
  • Restrict access to signature endpoints
  • Store signatures securely (consider encryption)
  • Implement rate limiting

Certificate Validation

  • Expiry date
  • Revocation status (OCSP/CRL)
  • Trusted CA
  • Signature integrity

Audit Trail

php
$signature = Signature::find($id);
$audits = $signature->audits;

foreach ($audits as $audit) {
    echo $audit->action . ' by user ' . $audit->user_id;
}

FAQ

What certificates are supported?

  • Spanish DNIe
  • FNMT
  • ACCV
  • Camerfirma
  • Any certificate recognized by the Spanish Administration or Eusko Jaularitza (Basque Administration)

Can I sign formats other than PDF?
Yes: PAdES, XAdES, CAdES

How do I verify a signed document?

  1. Use ValidateSignatureAction
  2. Open in Adobe Reader
  3. Use the VALIDe government platform

Is AutoFirma always required to be running?
Only when signing. Not needed for viewing/downloading signed docs.

Where are signed documents stored?

php
'storage' => [
    'disk' => 'public',
    'signed_documents_path' => 'documents/signed',
],

Support

AutoFirma Installation

Download from:
https://firmaelectronica.gob.es/Home/Descargas.html