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