vatsake / smart-id-v3
PHP Client Library for Smart ID v3 API
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/vatsake/smart-id-v3
Requires
- php: ^8.1
- php-http/discovery: ^1.20.0
- phpseclib/phpseclib: ~3.0
- psr/http-client-implementation: ^1.0.1
- psr/http-factory-implementation: *
- psr/log: ^3.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.10
- monolog/monolog: ^3.10
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-02-23 21:18:21 UTC
README
Warning
Development Status
This library is currently under active development and has not yet reached a stable release.
A modern PHP library for interacting with the Smart ID v3 API with comprehensive support for authentication, digital signatures, and certificate management.
Table of Contents
- Installation
- Requirements
- Quick Start
- Configuration
- Usage Flows
- Response Validation
- Error Handling
- Troubleshooting
- Resources
- Testing
- Todo
Quick Start
use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Constants\SmartIdBaseUrl; use Vatsake\SmartIdV3\SmartId; use Vatsake\SmartIdV3\Enums\DeviceLinkType; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Utils\RpChallenge; use Vatsake\SmartIdV3\Requests\AuthRequest; // 1. Initialize client $config = new SmartIdConfig( relyingPartyUUID: '00000000-0000-4000-8000-000000000000', relyingPartyName: 'DEMO', baseUrl: SmartIdBaseUrl::DEMO, // Use SmartIdBaseUrl::PROD for live certificatePath: '/path/to/mixed/certificates/folder' ); $smartId = new SmartId($config); // 2. Create authentication request $rpChallenge = RpChallenge::generate(); $request = AuthRequest::builder() ->withInteractions('Confirm login', 'You are signing into "myapp"') ->withRpChallenge($rpChallenge, HashAlgorithm::SHA_256) ->build(); // 3. Start device link authentication $session = $smartId->deviceLink()->auth()->startAnonymous($request); // 4. Send this to frontend every second $session->getDeviceLink(DeviceLinkType::QR); // 5. Poll for session completion $response = $smartId->session($session) ->withPolling(1000) // Poll every 1 second ->getAuthSession(30000); // 30 second timeout // 6. Validate response $response->validate() ->withCertificateValidation(true) ->check(); echo "Authentication successful!";
Installation
Install via Composer:
composer require vatsake/smart-id-v3
This library uses php-http/discovery to automatically detect and use PSR-18 HTTP clients. You'll need to install a compatible HTTP client implementation:
composer require guzzlehttp/guzzle
Other compatible clients: symfony/http-client, curl-client, etc.
Requirements
- PHP >= 8.1
- PSR-18 HTTP Client implementation
- PSR-17 HTTP Factory implementation
Configuration
Certificate Setup
The library requires certificates to validate certificate chains. Choose one of two setup modes:
Option 1: Direct Paths (Recommended)
Organize your certificates into separate directories for CA and intermediate certificates:
- Provide separate directories for CA and intermediate certificates using
caPathandintPath - Use this when certificates are already organized
Option 2: Auto-Separation
Use a single directory with mixed certificates and let the library organize them:
- Provide a single
certificatePathwith mixed certificates - The library automatically separates them based on certificate properties
Caching Details:
- All certificates must be in PEM format
- The library validates certificate chains using
openssl_x509_checkpurpose() - Certificates are automatically cached in
{temp-dir}/smart-id-cert-bundles/{hash}/for performance - The hash is computed from certificate file contents, ensuring cache integrity
- When certificates are added, removed, or modified, the hash changes automatically
- A new bundle folder is generated on next initialization
- Old bundles are automatically cleaned up
Client Initialization
Initialize the Smart ID client with SmartIdConfig:
Using Direct Certificate Paths:
use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Constants\SmartIdBaseUrl; $config = new SmartIdConfig( relyingPartyName: 'DEMO', relyingPartyUUID: '00000000-0000-4000-8000-000000000000', baseUrl: SmartIdBaseUrl::DEMO, caPath: '/path/to/ca-certificates', intPath: '/path/to/intermediate-certificates' );
Using Auto-Separation:
$config = new SmartIdConfig( relyingPartyUUID: 'DEMO', relyingPartyName: '00000000-0000-4000-8000-000000000000', baseUrl: SmartIdBaseUrl::DEMO, certificatePath: '/path/to/mixed-certificates' );
With HTTP Client and Logging:
For production deployments, configure a custom HTTP client and enable logging:
$client = new \GuzzleHttp\Client(); $log = new Monolog\Logger('smart-id'); $log->pushHandler(new Monolog\Handler\StreamHandler('php://stdout')); $config = new SmartIdConfig( relyingPartyUUID: 'DEMO', relyingPartyName: '00000000-0000-4000-8000-000000000000', baseUrl: SmartIdBaseUrl::DEMO, certificatePath: '/path/to/certificates', httpClient: $client, logger: $log );
SSL/HTTPS Configuration
Smart ID requires enhanced security measures including HTTPS key pinning. Configure your HTTP client to use public key pinning:
Option 1: Strict Key Pinning (Recommended for Production)
Pin directly to the Smart ID server's public key:
$client = new \GuzzleHttp\Client([ 'curl' => [ CURLOPT_PINNEDPUBLICKEY => 'sha256//....', ], ]);
Option 2: Intermediate Pinning
Pin to Smart ID's intermediate certificate instead of the leaf certificate. This reduces the need to update your configuration annually:
$client = new \GuzzleHttp\Client([ 'verify' => '/path/to/cert/bundle', // Contains only Smart ID's intermediate certificate 'curl' => [ CURLOPT_CAPATH => '/path/that/does/not/exist', // Disable system truststore ], ]);
Note: While intermediate pinning is more convenient, strict key pinning provides stronger security guarantees.
Usage Flows
Choosing the Right Flow
Smart ID supports multiple authentication and signature flows depending on your use case:
| Flow | User Initiation | Use Case |
|---|---|---|
| Device Link | User scans QR or opens app | Mobile-first, modern applications |
| Notification | Server initiates | Traditional web applications; less secure |
Device Link Flows
Device Link Flows allow users to initiate sessions themselves by scanning a QR code or pressing a button on a website/app that opens the Smart ID application directly.
Device Link Advantages:
- User controls the flow
- QR codes can be refreshed during polling
- Supports Web2App and App2App
- Better mobile user experience
Anonymous authentication session
Authenticate a user without requiring them to provide identity information upfront.
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Enums\DeviceLinkType; use Vatsake\SmartIdV3\Requests\AuthRequest; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Utils\RpChallenge; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $rpChallenge = RpChallenge::generate(); $request = AuthRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withRpChallenge($rpChallenge, HashAlgorithm::SHA_256) ->withInitialCallbackUrl('https://localhost/callback') // Mandatory for Web2App/App2App flows ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->deviceLink()->auth()->startAnonymous($request); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getAuthSession(10000); // Poll timeout on SMART-ID side; you might need to specify your httpclient's timeout accordingly // During status polling, refresh QR-code every second $session->getDeviceLink(DeviceLinkType::QR);
Identified authentication - National ID
Authenticate a specific user by their national identification number. Use when you know who the user is beforehand.
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Enums\DeviceLinkType; use Vatsake\SmartIdV3\Requests\AuthRequest; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Identity\SemanticsIdentifier; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Enums\NaturalIdentityType; use Vatsake\SmartIdV3\Utils\RpChallenge; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $identifier = SemanticsIdentifier::builder() ->withType(NaturalIdentityType::NATIONAL_PERSONAL_NUMBER) ->withCountryCode('EE') ->withIdentifier('40404040009') ->build(); $rpChallenge = RpChallenge::generate(); $request = AuthRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withRpChallenge($rpChallenge, HashAlgorithm::SHA_256) ->withInitialCallbackUrl('https://localhost/callback') // Mandatory for Web2App/App2App flows ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->deviceLink()->auth()->startEtsi($request, $identifier); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getAuthSession(10000); // Poll timeout on SMART-ID side // During status polling, refresh QR-code every second $session->getDeviceLink(DeviceLinkType::QR);
Identified authentication - Document Number
Authenticate a user using their Smart ID document number. Use when you know user's document number.
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Enums\DeviceLinkType; use Vatsake\SmartIdV3\Requests\AuthRequest; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Utils\RpChallenge; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $rpChallenge = RpChallenge::generate(); $request = AuthRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withRpChallenge($rpChallenge, HashAlgorithm::SHA_256) ->withInitialCallbackUrl('https://localhost/callback') // Mandatory for Web2App/App2App flows ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->deviceLink()->auth()->startDocument($request, 'PNOEE-40404040009-MOCK-Q'); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getAuthSession(10000); // Poll timeout on SMART-ID side // During status polling, refresh QR-code every second $session->getDeviceLink(DeviceLinkType::QR);
Signature with Device Link
Anonymous signature with certificate selection
Allow user to choose which certificate to use for signing:
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Enums\DeviceLinkType; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Enums\NaturalIdentityType; use Vatsake\SmartIdV3\Identity\SemanticsIdentifier; use Vatsake\SmartIdV3\Requests\DeviceLinkCertChoiceRequest; use Vatsake\SmartIdV3\Requests\LinkedRequest; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); // Step 1: Certificate selection $request = DeviceLinkCertChoiceRequest::builder() ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withInitialCallbackUrl('https://localhost/callback') // Mandatory for Web2App/App2App flows ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->deviceLink()->signing()->startAnonymousCertChoice($request); $_SESSION['session'] = $session; // Save it for later // Get certificate selection result $response = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getCertChoiceSession(10000); // Poll timeout on SMART-ID side $_SESSION['documentNo'] = $response->identifier; // Save it for later // During status polling, refresh QR-code every second $session->getDeviceLink(DeviceLinkType::QR); // Step 2: Sign with selected certificate (after successful cert-choice) $dataToSign = "hello world"; $request = LinkedRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withLinkedSessionId($session->getSessionId()) ->withData($dataToSign, HashAlgorithm::SHA_256) ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withInitialCallbackUrl('https://localhost/callback') // Mandatory for Web2App/App2App flows ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $documentNo = $_SESSION['documentNo']; $smartId->notification()->signing()->startLinkedSigning($request, $documentNo); // Get session status $response = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getSigningSession(10000); // Poll timeout on SMART-ID side
Signature with national ID
Sign documents for a specific user identified by national ID:
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Enums\DeviceLinkType; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Enums\NaturalIdentityType; use Vatsake\SmartIdV3\Identity\SemanticsIdentifier; use Vatsake\SmartIdV3\Requests\DeviceLinkSigningRequest; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $identifier = SemanticsIdentifier::builder() ->withType(NaturalIdentityType::NATIONAL_PERSONAL_NUMBER) ->withCountryCode('EE') ->withIdentifier('40404040009') ->build(); $dataToSign = "hello world"; $request = DeviceLinkSigningRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withData($dataToSign, HashAlgorithm::SHA_256) ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withInitialCallbackUrl('https://localhost/callback') // Mandatory for Web2App/App2App flows ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->deviceLink()->signing()->startEtsi($request, $identifier); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getSigningSession(10000); // Poll timeout on SMART-ID side // During status polling, refresh QR-code every second $session->getDeviceLink(DeviceLinkType::QR);
Signature with document number
Sign documents for a user identified by document number:
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Enums\DeviceLinkType; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Requests\DeviceLinkSigningRequest; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $dataToSign = "hello world"; $request = DeviceLinkSigningRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withData($dataToSign, HashAlgorithm::SHA_256) ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withInitialCallbackUrl('https://localhost/callback') // Mandatory for Web2App/App2App flows ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->deviceLink()->signing()->startDocument($request, 'PNOEE-40404040009-MOCK-Q'); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getSigningSession(10000); // Poll timeout on SMART-ID side // During status polling, refresh QR-code every second $session->getDeviceLink(DeviceLinkType::QR);
Notification Flows
Notification Flows are initiated by the server and push notifications to the user's mobile device. This is the traditional Smart ID flow.
Notification Advantages:
- Server-initiated, predictable flow
- No QR code generation or refresh needed
Authentication - National ID
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Enums\DeviceLinkType; use Vatsake\SmartIdV3\Requests\AuthRequest; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Identity\SemanticsIdentifier; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Enums\NaturalIdentityType; use Vatsake\SmartIdV3\Utils\RpChallenge; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $identifier = SemanticsIdentifier::builder() ->withType(NaturalIdentityType::NATIONAL_PERSONAL_NUMBER) ->withCountryCode('EE') ->withIdentifier('40504040001') ->build(); $rpChallenge = RpChallenge::generate(); $request = AuthRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withRpChallenge($rpChallenge, HashAlgorithm::SHA_256) ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->notification()->auth()->startEtsi($request, $identifier); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getAuthSession(10000); // Poll timeout on SMART-ID side
Authentication - Document Number
Authenticate a user using their Smart ID document number (server-initiated):
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Requests\AuthRequest; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Utils\RpChallenge; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $rpChallenge = RpChallenge::generate(); $request = AuthRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withRpChallenge($rpChallenge, HashAlgorithm::SHA_256) ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->notification()->auth()->startDocument($request, 'PNOEE-40504040001-DEM0-Q'); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getAuthSession(10000); // Poll timeout on SMART-ID side
Signature - National ID
Sign documents for a user identified by national ID (server-initiated):
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Enums\NaturalIdentityType; use Vatsake\SmartIdV3\Identity\SemanticsIdentifier; use Vatsake\SmartIdV3\Requests\NotificationSigningRequest; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $identifier = SemanticsIdentifier::builder() ->withType(NaturalIdentityType::NATIONAL_PERSONAL_NUMBER) ->withCountryCode('EE') ->withIdentifier('40504040001') ->build(); $dataToSign = "hello world"; $request = NotificationSigningRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withData($dataToSign, HashAlgorithm::SHA_256) ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->notification()->signing()->startEtsi($request, $identifier); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getSigningSession(10000); // Poll timeout on SMART-ID side
Signature - Document Number
Sign documents for a user identified by document number (server-initiated):
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Requests\NotificationSigningRequest; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $dataToSign = "hello world"; $request = NotificationSigningRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withData($dataToSign, HashAlgorithm::SHA_256) ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->notification()->signing()->startDocument($request, 'PNOEE-40504040001-DEM0-Q'); $_SESSION['session'] = $session; // Save it for later // Get session status $ses = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getSigningSession(10000); // Poll timeout on SMART-ID side
Signature with Certificate Selection - National ID
Allow a user to select their certificate before signing. First perform a certificate choice, then sign with the selected certificate:
use Vatsake\SmartIdV3\Enums\CertificateLevel; use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\Enums\HashAlgorithm; use Vatsake\SmartIdV3\Enums\NaturalIdentityType; use Vatsake\SmartIdV3\Identity\SemanticsIdentifier; use Vatsake\SmartIdV3\Requests\NotificationCertChoiceRequest; use Vatsake\SmartIdV3\Requests\NotificationSigningRequest; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); $identifier = SemanticsIdentifier::builder() ->withType(NaturalIdentityType::NATIONAL_PERSONAL_NUMBER) ->withCountryCode('EE') ->withIdentifier('40504040001') ->build(); // Step 1: Let user choose certificate $request = NotificationCertChoiceRequest::builder() ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $session = $smartId->notification()->signing()->startCertChoice($request, $identifier); $_SESSION['session'] = $session; // Save it for later // Get certificate selection result $response = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getCertChoiceSession(10000); // Poll timeout on SMART-ID side $_SESSION['documentNo'] = $response->identifier; // Save it for later // Step 2: Sign with selected certificate (after successful cert-choice) $dataToSign = "hello world"; $request = NotificationSigningRequest::builder() ->withInteractions( 'display text up to 60 characters', 'display text up to 200 characters' ) ->withData($dataToSign, HashAlgorithm::SHA_256) ->withCertificateLevel(CertificateLevel::QUALIFIED) // Optional ->withRequestProperties(true) // Optional; Whether SMART-ID adds user's IP to response ->build(); $documentNo = $_SESSION['documentNo']; $smartId->notification()->signing()->startDocument($request, $documentNo); // Get session status $response = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getSigningSession(10000); // Poll timeout on SMART-ID side
Certificate Retrieval
Retrieve a user's signing certificate without initiating a full authentication or signing session:
use Vatsake\SmartIdV3\Config\SmartIdConfig; use Vatsake\SmartIdV3\SmartId; $config = new SmartIdConfig(...); $smartId = new SmartId($config); // Retrieve certificate for a document $smartId->getSigningCertificate('PNOEE-40504040001-DEM0-Q');
Response Validation
After receiving a response from Smart ID, you should always validate the response to ensure the signature is valid, the certificate is trusted, and the certificate hasn't been revoked. The library provides a fluent validation API:
use Vatsake\SmartIdV3\Exceptions\Validation\ValidationException; // Client signed something /** @var Vatsake\SmartIdV3\Session\SigningSession */ $response = $smartId->session($session) ->withPolling(1000) // Optional, will poll until session is completed ->getSigningSession(10000); // Poll timeout on SMART-ID side try { $response->validate() ->withSignatureValidation(true) // Validates the signature (enabled by default) ->withCertificateValidation(true) // Validates certificate chain ->withRevocationValidation(true) // Checks if certificate is revoked ->check(); // Response is valid, proceed with your logic echo "Signature is valid!"; } catch (IncompleteSessionException $e) { // Session not complete yet } catch (UserRefusedException $e) { // User refused the request } catch (SessionTimeoutException $e) { // Session timed out } catch (DocumentUnusableException $e) { // Document unusable - check Smart-ID app for details } catch (WrongVcException $e) { // User entered wrong verification code } catch (RequiredInteractionNotSupportedByAppException $e) { // User's app doesn't support required interaction } catch (UserRefusedCertChoiceException $e) { // User refused certificate choice } catch (UserRefusedInteractionException $e) { // User cancelled on interaction screen } catch (ProtocolFailureException $e) { // Logical error in signing protocol } catch (ExpectedLinkedSessionException $e) { // App received different transaction instead of linked session } catch (ServerErrorException $e) { // Smart-ID server error } catch (ValidationException $e) { // Validation of the response failed // Specific validation exceptions: // - CertificateChainException: Certificate chain validation failed // - OcspCertificateRevocationException: Certificate is revoked // - OcspKeyUsageException: OCSP responder cert missing key usage // - OcspResponseTimeException: OCSP response time outside acceptable range // - OcspSignatureException: OCSP response signature validation failed // - CertificateKeyUsageException: Certificate key usage validation failed // - CertificatePolicyException: Certificate missing required policy OIDs // - CertificateQcException: Certificate missing required QC statements } catch (Exception $e) { // Something unexpected went wrong }
Error Handling
Exception Hierarchy
Exceptions are organized in a hierarchy to help with error handling:
Session State Exceptions - User or session state issues:
| Exception | Description |
|---|---|
IncompleteSessionException |
The session is not complete yet. Try polling again. |
UserRefusedException |
User refused the request (e.g., rejected in Smart-ID app). |
SessionTimeoutException |
Session timed out. User didn't respond in time. |
DocumentUnusableException |
Request failed. Check Smart-ID app logs or contact support. |
User Interaction Exceptions - Issues with user interactions:
| Exception | Description |
|---|---|
WrongVcException |
User entered the wrong verification code. |
RequiredInteractionNotSupportedByAppException |
User's app version doesn't support required interactions. |
UserRefusedCertChoiceException |
User refused to choose a certificate. |
UserRefusedInteractionException |
User cancelled on the interaction screen. |
Protocol Exceptions - Protocol-level issues:
| Exception | Description |
|---|---|
ProtocolFailureException |
A logical error occurred in the signing protocol. |
ExpectedLinkedSessionException |
App received different transaction instead of linked session. |
ServerErrorException |
Smart-ID server returned an error. |
Validation Exceptions - Certificate and signature validation failures:
| Exception | Description |
|---|---|
ValidationException |
Generic validation failure (parent of others below). |
CertificateChainException |
Certificate chain validation failed. Not trusted. |
OcspCertificateRevocationException |
OCSP status indicates certificate is revoked. |
OcspKeyUsageException |
OCSP responder certificate missing OCSP signing key usage. |
OcspResponseTimeException |
OCSP response time outside acceptable clock skew window. |
OcspSignatureException |
OCSP response signature validation failed. |
CertificateKeyUsageException |
Certificate key usage validation failed. |
CertificatePolicyException |
Certificate missing required Smart-ID policy OIDs. |
CertificateQcException |
Certificate missing required QC statements. |
Best Practices
- Always validate responses - Never trust unvalidated signatures or certificates
- Enable all validations - Use
withCertificateValidation(true),withRevocationValidation(true), andwithSignatureValidation(true) - Log exceptions - Capture exception details for debugging and monitoring
- Provide user feedback - Inform users of temporary issues (timeouts, refusal) vs. permanent errors (revoked certificate)
Troubleshooting
Common Issues
Certificate validation failures
If you're getting CertificateChainException or other certificate errors:
- Verify your certificate files are in PEM format
- Check that the path is correct
- Ensure certificate file permissions allow reading (644 or similar)
OCSP validation errors
If you're getting OcspCertificateRevocationException or other OCSP errors:
- Verify your system has correct time/date (OCSP responses include time validation)
- Check network connectivity to OCSP responders
- Temporarily disable revocation validation for debugging:
->withRevocationValidation(false)
Errors when scanning QR code
- Ensure QR codes are refreshed every 1-2 seconds during polling
- Verify QR code is large enough to scan
- Check that
withInitialCallbackUrlis set correctly for Web2App/App2App flows - Confirm the callback URL is accessible from the user's device
Debug Logging
Enable detailed logging to troubleshoot issues:
use Monolog\Logger; use Monolog\Handler\StreamHandler; $logger = new Logger('smart-id'); $logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG)); $config = new SmartIdConfig( ... logger: $logger );
Resources
Official Documentation:
Testing
Run tests with PHPUnit:
./vendor/bin/phpunit
Note
Integration tests make real API calls to Smart-ID (DEMO environment).
Todo
- Figure out why mocking doesn't work in demo environment
- Test App2App and Web2App flows
- More stuff