sk-id-solutions / smart-id-php-client
Smart-ID PHP client
Requires
- php: >=8.4
- ext-curl: *
- ext-json: *
- ext-openssl: *
- guzzlehttp/guzzle: ^7.0
- guzzlehttp/psr7: ^2.0
- phpseclib/phpseclib: ~3.0
- psr/log: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.65
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.5
This package is not auto-updated.
Last update: 2026-03-30 12:16:16 UTC
README
This library supports Smart-ID API v3.
Table of contents
- Introduction
- Features
- Requirements
- Getting the library
- Changelog
- How to use it with RP API v3
- Logging
Introduction
The Smart-ID PHP client can be used for easy integration of the Smart-ID solution to information systems or e-services.
Features
- User authentication (device link and notification-based flows)
- SmartIdClient facade for simplified integration
- Built-in HTTPS public key pinning for secure API communication
- QR code and Web2App device link URL generation
- ACSP_V2 signature protocol with RSA-PSS verification
- SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-384, SHA3-512 hash algorithm support
- Certificate trust chain validation with full signatureAlgorithmParameters validation
- OCSP certificate revocation checking via AIA or designated responder
- Verification code calculation
- CallbackUrlUtil for Web2App/App2App callback URL creation and validation
- Optional PSR-3 logging support
Requirements
- PHP >= 8.4
- ext-openssl (for certificate parsing and signature verification)
- ext-json (for API request/response serialization)
- ext-curl (for HTTPS pinning via
CURLOPT_PINNEDPUBLICKEY)
Getting the library
Install via Composer:
composer require sk-id-solutions/smart-id-php-client
Changelog
Changes introduced with new library versions are described in CHANGELOG.md.
How to use it with RP API v3
Import the relevant classes from the Sk\SmartId namespace:
use Sk\SmartId\Api\SmartIdRestConnector; use Sk\SmartId\Ssl\SslPinnedPublicKeyStore;
Test accounts for testing
Note: Smart-ID Basic level accounts (certificate level
ADVANCED) are not supported in the demo environment.
Uploading certificates to demo OCSP
Smart-ID demo certificates are not automatically available in the demo AIA OCSP responder (aia.demo.sk.ee).
To make them available for OCSP revocation checking, upload them via SK's certificate upload interface:
- Upload page: https://demo.sk.ee/upload_cert/ — upload certificates and set their OCSP status (good/revoked/unknown)
- Download demo certificates: https://sid.demo.sk.ee/portal/login — log in to the Smart-ID self-service portal to download certificates
- Demo OCSP info: https://github.com/SK-EID/ocsp/wiki/SK-OCSP-Demo-environment
Setting up SmartIdClient
SmartIdClient is the main entry point for using Smart-ID services. It wires together the connector, session status poller, and request builders.
use Sk\SmartId\SmartIdClient; use Sk\SmartId\Ssl\SslPinnedPublicKeyStore; // Demo environment $client = new SmartIdClient( relyingPartyUUID: '00000000-0000-4000-8000-000000000000', relyingPartyName: 'DEMO', hostUrl: 'https://sid.demo.sk.ee/smart-id-rp/v3', sslPinnedKeys: SslPinnedPublicKeyStore::loadDemo(), ); // Production environment with logging (see "Logging" section below) $sslKeys = SslPinnedPublicKeyStore::create() ->addPublicKeyHash('sha256//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=') ->addPublicKeyHash('sha256//YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY='); $client = new SmartIdClient( relyingPartyUUID: 'your-relying-party-uuid', relyingPartyName: 'Your RP Name', hostUrl: 'https://rp-api.smart-id.com/v3', sslPinnedKeys: $sslKeys, logger: $logger, // optional PSR-3 LoggerInterface ); // Create authentication builders directly from the client $deviceLinkBuilder = $client->createDeviceLinkAuthentication(); $notificationBuilder = $client->createNotificationAuthentication(); // Get the session status poller $poller = $client->getSessionStatusPoller(); // Create validator (OCSP revocation checking is enabled automatically) $validator = $client->createAuthenticationResponseValidator(); // Configure polling parameters (optional) $client->setPollTimeoutMs(30000); $client->setPollIntervalMs(1000);
You can also use the connector directly for lower-level control (see Setting up the connector).
Setting up the connector
Configure to use with Smart-ID Demo environment
use Sk\SmartId\Api\SmartIdRestConnector; use Sk\SmartId\Ssl\SslPinnedPublicKeyStore; // Demo environment $connector = new SmartIdRestConnector( 'https://sid.demo.sk.ee/smart-id-rp/v3', SslPinnedPublicKeyStore::loadDemo(), ); // Production environment $sslKeys = SslPinnedPublicKeyStore::create() ->addPublicKeyHash('sha256//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=') ->addPublicKeyHash('sha256//YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY='); $connector = new SmartIdRestConnector('https://rp-api.smart-id.com/v3', $sslKeys);
Setting up HTTPS public key pinning
HTTPS public key pinning is used to prevent man-in-the-middle attacks against the Smart-ID API connection.
Live SSL certificates: https://sk-eid.github.io/smart-id-documentation/https_pinning.html#_rp_api_smart_id_com_certificates
Demo SSL certificates: https://sk-eid.github.io/smart-id-documentation/https_pinning.html#_sid_demo_sk_ee_certificates
Demo / testing
For development against sid.demo.sk.ee, the SDK bundles demo keys:
$connector = new SmartIdRestConnector( 'https://sid.demo.sk.ee/smart-id-rp/v3', SslPinnedPublicKeyStore::loadDemo(), );
Providing SSL public key hashes manually
For production, you must configure your own SPKI hashes. Download the current certificates from SK's HTTPS pinning documentation and extract each certificate's SPKI hash:
openssl x509 -inform PEM -in certificate.pem -noout -pubkey \ | openssl rsa -pubin -outform der 2>/dev/null \ | openssl dgst -sha256 -binary \ | openssl enc -base64
Then provide the hashes:
$sslKeys = SslPinnedPublicKeyStore::create() ->addPublicKeyHash('sha256//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=') ->addPublicKeyHash('sha256//YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY='); $connector = new SmartIdRestConnector('https://rp-api.smart-id.com/v3', $sslKeys);
Loading hashes from a directory
You can load hashes from a directory of .key files (each containing one sha256//... hash):
$sslKeys = SslPinnedPublicKeyStore::loadFromDirectory('/path/to/your/keys'); $connector = new SmartIdRestConnector('https://rp-api.smart-id.com/v3', $sslKeys);
Loading hashes from an environment variable
When hashes are stored in a single environment variable (e.g. from .env or container config):
SMARTID_SSL_PINS="sha256//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=,sha256//YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY="
$sslKeys = SslPinnedPublicKeyStore::fromString(getenv('SMARTID_SSL_PINS')); $connector = new SmartIdRestConnector('https://rp-api.smart-id.com/v3', $sslKeys);
A custom separator can be provided as the second argument:
// Semicolon-separated $sslKeys = SslPinnedPublicKeyStore::fromString(getenv('SMARTID_SSL_PINS'), ';');
Loading hashes from an array (secret managers, config files)
When your secret manager or configuration returns an array of hash strings:
$hashes = $secretManager->getSecret('smartid-ssl-pins'); // returns string[] $sslKeys = SslPinnedPublicKeyStore::fromArray($hashes); $connector = new SmartIdRestConnector('https://rp-api.smart-id.com/v3', $sslKeys);
All methods that accept hashes (addPublicKeyHash(), fromString(), fromArray()) validate
every hash immediately and throw \InvalidArgumentException if the format is invalid or the input is empty.
Device link flows
Device link flows are the more secure way to ensure the user who started the authentication is in control of the device. More info: https://sk-eid.github.io/smart-id-documentation/rp-api/device_link_flows.html
Device link authentication session
Request parameters
- relyingPartyUUID — Required. UUID of the Relying Party.
- relyingPartyName — Required. Friendly name of the Relying Party, limited to 32 bytes in UTF-8 encoding.
- certificateLevel — Level of certificate requested. Possible values:
QUALIFIED,ADVANCED. Defaults toQUALIFIED. - hashAlgorithm — Hash algorithm for signatures. Supported:
SHA-256,SHA-384,SHA-512,SHA3-256,SHA3-384,SHA3-512. Defaults toSHA-512. - interactions — Required. Array of
DeviceLinkInteractionobjects defining the allowed interactions in order of preference. - initialCallbackUrl — Optional. HTTPS callback URL for Web2App same-device flows. Set via
withCallbackUrl()on the builder. - nonce — Optional. Random string, up to 30 characters. Used to override idempotent behaviour (if the same request is made within a 15-second window, the same response is returned unless a nonce is provided).
- capabilities — Optional. Array of capability strings. Used only when agreed with Smart-ID provider.
- requestProperties — Optional. Set
shareMdClientIpAddresstotrueto request the IP address of the user's device (see Requesting IP address).
Response parameters
- sessionID — String used to query the session status.
- sessionToken — Unique token linking this session between RP, RP-API, and the mobile app.
- sessionSecret — Base64-encoded secret key. Keep on backend only, never expose to client.
- deviceLinkBase — Base URI for forming device link or QR code URLs.
Using the request builder
use Sk\SmartId\DeviceLink\DeviceLinkAuthenticationRequestBuilder; use Sk\SmartId\Enum\CertificateLevel; use Sk\SmartId\DeviceLink\DeviceLinkInteraction; $builder = new DeviceLinkAuthenticationRequestBuilder( $connector, '00000000-0000-4000-8000-000000000000', // relyingPartyUUID 'DEMO', // relyingPartyName ); // Initiate anonymous authentication (no user identifier needed) $session = $builder ->withCertificateLevel(CertificateLevel::QUALIFIED) ->withAllowedInteractionsOrder([ DeviceLinkInteraction::displayTextAndPin('Log in to example.com'), ]) ->initiate(); // Session ID for polling $sessionId = $session->getSessionId(); // Verification code to display (if using notification-style interaction) $verificationCode = $session->getVerificationCode(); // Build QR code URL (see "Generating QR code or device link" section) $qrCodeUrl = $session->buildQrCodeUrl();
The builder automatically generates the RP challenge and calculates the verification code. If needed, you can provide your own RP challenge:
use Sk\SmartId\Util\RpChallengeGenerator; $rpChallenge = RpChallengeGenerator::generate(); $session = $builder ->withRpChallenge($rpChallenge) ->withAllowedInteractionsOrder([ DeviceLinkInteraction::displayTextAndPin('Log in to example.com'), ]) ->initiate();
Using request objects directly
For lower-level control, construct request objects directly:
use Sk\SmartId\DeviceLink\DeviceLinkAuthenticationRequest; use Sk\SmartId\Enum\CertificateLevel; use Sk\SmartId\Enum\HashAlgorithm; use Sk\SmartId\DeviceLink\DeviceLinkInteraction; use Sk\SmartId\Util\RpChallengeGenerator; // For security, generate a new challenge for each request $rpChallenge = RpChallengeGenerator::generate(); $interactions = [DeviceLinkInteraction::displayTextAndPin('Log in to example.com')]; $request = new DeviceLinkAuthenticationRequest( relyingPartyUUID: '00000000-0000-4000-8000-000000000000', relyingPartyName: 'DEMO', rpChallenge: $rpChallenge, hashAlgorithm: HashAlgorithm::SHA512, allowedInteractionsOrder: $interactions, certificateLevel: CertificateLevel::QUALIFIED, ); $response = $connector->initiateDeviceLinkAuthentication($request); // Store these on the backend for later use $sessionId = $response->getSessionID(); $sessionToken = $response->getSessionToken(); $sessionSecret = $response->getSessionSecret(); // Keep secret, do not expose to client $deviceLinkBase = $response->getDeviceLinkBase();
Generating QR code or device link
Documentation: https://sk-eid.github.io/smart-id-documentation/rp-api/device_link_flows.html
To use the Smart-ID demo environment, you must specify SchemeName::DEMO as the scheme name
(use ->withDemoEnvironment() or ->withSchemeName(SchemeName::DEMO)).
See: https://sk-eid.github.io/smart-id-documentation/environments.html#_demo
Device link parameters
- deviceLinkBase — Value from session-init response.
- deviceLinkType —
QRorWEB2APP. - sessionToken — Token from the session response.
- elapsedSeconds — Seconds since the session-init response was received. Required for QR codes.
- lang — User language. Default:
eng. - schemeName — Controls environment. Default:
SchemeName::PRODUCTION. UseSchemeName::DEMOfor demo. - callbackUrl — Required for Web2App flows. Must be HTTPS.
- brokeredRpName — Optional. Name of the brokered Relying Party, used when acting as a broker. Included in authCode calculation and must match during validation.
Generating QR code URL
QR code URLs must be refreshed every second because the authCode changes based on elapsed time to prevent replay attacks.
// Using the session object (simplest approach) $session = $builder->initiate(); // QR code URL auto-calculates elapsed time from session creation $qrCodeUrl = $session->buildQrCodeUrl(); // Or provide elapsed seconds explicitly $qrCodeUrl = $session->buildQrCodeUrl(elapsedSeconds: 5);
Using the DeviceLinkBuilder for more control:
$qrCodeUrl = $session->createDeviceLinkBuilder() ->withElapsedSeconds($elapsedSeconds) ->withDemoEnvironment() // for demo environment ->withLang('est') // override language ->buildQrCodeUrl();
Generating Web2App URL
For mobile web browsers where the user can open the Smart-ID app directly:
use Sk\SmartId\Util\CallbackUrlUtil; use Sk\SmartId\Util\CallbackUrlValidator; // Validate and create callback URL with a cryptographically random token $callbackBase = 'https://your-app.com/callback'; $callbackResult = CallbackUrlUtil::createCallbackUrl($callbackBase); $callbackUrl = $callbackResult['callbackUrl']; // e.g., https://your-app.com/callback?value=<random-token> $callbackToken = $callbackResult['token']; // Store to verify the callback later // Callback URL must be set when initiating the session $session = $builder ->withCallbackUrl($callbackUrl) ->withAllowedInteractionsOrder([ DeviceLinkInteraction::displayTextAndPin('Log in'), ]) ->initiate(); $web2AppUrl = $session->buildWeb2AppUrl();
Overriding default values
$builder = $session->createDeviceLinkBuilder() ->withDemoEnvironment() // override scheme for demo ->withLang('est') // override language ->withElapsedSeconds($elapsed); $qrCodeUrl = $builder->buildQrCodeUrl();
Examples of allowed device link interactions
An app can support different interaction types, and a Relying Party can specify preferred interactions with or without fallback options.
For device link flows, the available interaction types are limited to displayTextAndPIN and confirmationMessage.
displayTextAndPIN is used for short text with PIN-code input, while confirmationMessage is used for longer text with Confirm and Cancel buttons
and a second screen to enter the PIN-code.
Example 1: confirmationMessage with fallback to displayTextAndPIN
The RP's first choice is confirmationMessage; if not available, then fall back to displayTextAndPIN.
$builder->withAllowedInteractionsOrder([ DeviceLinkInteraction::confirmationMessage('Up to 200 characters of text here..'), DeviceLinkInteraction::displayTextAndPin('Up to 60 characters of text here..'), ]);
Example 2: confirmationMessage only (no fallback)
If the interaction is not supported by the app, the process will fail if no fallback is provided.
$builder->withAllowedInteractionsOrder([ DeviceLinkInteraction::confirmationMessage('Up to 200 characters of text here..'), ]);
Notification-based flows
Differences between notification-based and device link flows
- Notification-based flow — Push notification is sent directly to the user's Smart-ID app. Requires knowing the user's identity (document number or semantics identifier) beforehand. More vulnerable to phishing; recommended to use after user identity has been established via device link flow. No QR codes needed.
- Device link flow — Generates QR codes or deep links. Supports anonymous authentication where the user's identity is not required beforehand. QR code must be refreshed every second.
Notification-based authentication session
Request parameters
- relyingPartyUUID — Required. UUID of the Relying Party.
- relyingPartyName — Required. Friendly name of the Relying Party.
- documentNumber or semanticsIdentifier — Required (one of). Identifies the user.
- certificateLevel — Optional.
QUALIFIEDorADVANCED. Defaults toQUALIFIED. - hashAlgorithm — Optional. Supported:
SHA-256,SHA-384,SHA-512,SHA3-256,SHA3-384,SHA3-512. Defaults toSHA-512. - interactions — Required. Array of
NotificationInteractionobjects defining the allowed interactions in order of preference. - nonce — Optional. Random string, up to 30 characters. Used to override idempotent behaviour (if the same request is made within a 15-second window, the same response is returned unless a nonce is provided).
- capabilities — Optional. Array of capability strings.
- requestProperties — Optional. Set
shareMdClientIpAddresstotrueto request the IP address of the user's device (see Requesting IP address).
Response parameters
- sessionID — String used to query the session status.
Initiating with semantics identifier
More info about Semantics Identifiers: ETSI EN 319 412-1
use Sk\SmartId\Notification\NotificationAuthenticationRequestBuilder; use Sk\SmartId\Notification\NotificationInteraction; use Sk\SmartId\Enum\CertificateLevel; use Sk\SmartId\Model\SemanticsIdentifier; // Create semantics identifier: // Type: PNO (personal number), PAS (passport), IDC (national identity card) // Country: 2-letter ISO 3166-1 alpha-2 code $semanticsIdentifier = SemanticsIdentifier::forPerson('EE', '30303039914'); // Or from a full string: // $semanticsIdentifier = SemanticsIdentifier::fromString('PNOEE-30303039914'); $builder = new NotificationAuthenticationRequestBuilder( $connector, '00000000-0000-4000-8000-000000000000', // relyingPartyUUID 'DEMO', // relyingPartyName ); $session = $builder ->withSemanticsIdentifier($semanticsIdentifier) ->withCertificateLevel(CertificateLevel::QUALIFIED) ->withAllowedInteractionsOrder([ NotificationInteraction::confirmationMessageAndVerificationCodeChoice('Log in to example.com'), NotificationInteraction::displayTextAndPin('Log in to example.com'), ]) ->initiate(); // Display verification code to the user $verificationCode = $session->getVerificationCode(); // Use session ID to poll for status $sessionId = $session->getSessionId();
Jump to Querying session status for an example of session status polling.
Initiating with document number
$session = $builder ->withDocumentNumber('PNOLT-40504040001-MOCK-Q') ->withCertificateLevel(CertificateLevel::QUALIFIED) ->withAllowedInteractionsOrder([ NotificationInteraction::displayTextAndPin('Log in to example.com'), ]) ->initiate(); $verificationCode = $session->getVerificationCode(); $sessionId = $session->getSessionId();
Examples of allowed notification-based interactions
Notification-based flows support additional interaction types compared to device link flows.
Available types are displayTextAndPIN, confirmationMessage, and confirmationMessageAndVerificationCodeChoice.
Example 1: confirmationMessageAndVerificationCodeChoice with fallback to confirmationMessage and displayTextAndPIN
The RP's first choice is confirmationMessageAndVerificationCodeChoice; the second choice is confirmationMessage; the third choice is displayTextAndPIN.
$builder->withAllowedInteractionsOrder([ NotificationInteraction::confirmationMessageAndVerificationCodeChoice('Up to 200 characters of text here...'), NotificationInteraction::confirmationMessage('Up to 200 characters of text here...'), NotificationInteraction::displayTextAndPin('Up to 60 characters of text here...'), ]);
Example 2: confirmationMessageAndVerificationCodeChoice only (no fallback)
Process will fail if interaction is not supported and there is no fallback.
$builder->withAllowedInteractionsOrder([ NotificationInteraction::confirmationMessageAndVerificationCodeChoice('Up to 200 characters of text here...'), ]);
Querying session status
Session status response
The session status response includes various fields depending on whether the session has completed or is still running:
- state —
RUNNINGorCOMPLETE - result.endResult — Outcome of the session (see End result values below).
- result.documentNumber — Document number returned when
endResultisOK. Can be used in further authentication requests to target the same device. - signatureProtocol —
ACSP_V2for authentication. - signature — For
ACSP_V2: containsvalue,serverRandom,userChallenge,flowType,signatureAlgorithm,signatureAlgorithmParameters. - cert — Certificate info:
value(Base64-encoded X.509 DER) andcertificateLevel(QUALIFIEDorADVANCED). - interactionTypeUsed — The interaction type that was actually used for the session (e.g.,
displayTextAndPIN,confirmationMessage). - ignoredProperties — Array of property names from the request that were not recognized by the server.
- deviceIpAddress — IP address of the user's device, if
requestProperties.shareMdClientIpAddresswas set totrueand the feature is enabled for your account.
End result values
The result.endResult field may contain the following values:
OK— Session completed successfully.USER_REFUSED— User refused the session.USER_REFUSED_CERT_CHOICE— User has multiple accounts and pressed Cancel on device choice screen.USER_REFUSED_DISPLAYTEXTANDPIN— User pressed Cancel on thedisplayTextAndPINinteraction screen.USER_REFUSED_VC_CHOICE— User pressed Cancel on the verification code choice screen.USER_REFUSED_CONFIRMATIONMESSAGE— User pressed Cancel on theconfirmationMessagescreen.USER_REFUSED_CONFIRMATIONMESSAGE_WITH_VC_CHOICE— User pressed Cancel on theconfirmationMessageAndVerificationCodeChoicescreen.USER_REFUSED_INTERACTION— User pressed Cancel on the interaction screen.result.detailscontains info about which interaction was canceled.TIMEOUT— User did not respond in time.DOCUMENT_UNUSABLE— Session could not be completed due to an issue with the document.WRONG_VC— User selected the wrong verification code.REQUIRED_INTERACTION_NOT_SUPPORTED_BY_APP— The requested interaction is not supported by the user's app.PROTOCOL_FAILURE— An error occurred in the signing protocol.EXPECTED_LINKED_SESSION— Server expected a device link session but received a notification-based session (or vice versa).SERVER_ERROR— Technical error occurred at the server side and the process was terminated.ACCOUNT_UNUSABLE— The user's Smart-ID account is unusable for this operation.
End result to exception mapping
When using SessionStatusPoller, non-OK end results are automatically converted to typed exceptions:
| End result | Exception |
|---|---|
USER_REFUSED_INTERACTION |
UserRefusedInteractionException |
USER_REFUSED, USER_REFUSED_CERT_CHOICE, USER_REFUSED_DISPLAYTEXTANDPIN, USER_REFUSED_VC_CHOICE, USER_REFUSED_CONFIRMATIONMESSAGE, USER_REFUSED_CONFIRMATIONMESSAGE_WITH_VC_CHOICE |
UserRefusedException |
TIMEOUT |
SessionTimeoutException |
DOCUMENT_UNUSABLE |
DocumentUnusableException |
WRONG_VC |
WrongVerificationCodeException |
REQUIRED_INTERACTION_NOT_SUPPORTED_BY_APP |
RequiredInteractionNotSupportedException |
PROTOCOL_FAILURE |
ProtocolFailureException |
SERVER_ERROR |
ServerErrorException |
ACCOUNT_UNUSABLE, EXPECTED_LINKED_SESSION, other |
SmartIdException |
Note:
UserRefusedInteractionExceptionextendsUserRefusedException. The poller checks forUSER_REFUSED_INTERACTIONfirst, so catchingUserRefusedExceptionafterUserRefusedInteractionExceptionhandles all other user refusal variants.
Polling for final session status
Using SessionStatusPoller to poll until the session completes:
use Sk\SmartId\Session\SessionStatusPoller; $poller = new SessionStatusPoller($connector); // Poll until session completes (blocks with long polling) $sessionStatus = $poller->pollUntilComplete($sessionId); if ($sessionStatus->isComplete()) { $endResult = $sessionStatus->getResult()->getEndResult(); // 'OK' means authentication succeeded }
The poller automatically throws typed exceptions for error results (see Exception handling).
You can configure polling parameters:
$poller = new SessionStatusPoller($connector); $poller->setPollTimeoutMs(30000); // Server-side long poll timeout (default: 30s) $poller->setPollIntervalMs(1000); // Interval between poll attempts (default: 1s) // Limit the number of poll attempts $sessionStatus = $poller->pollUntilComplete($sessionId, maxAttempts: 60);
Single status query
For device link flows where you need to generate a fresh QR code every second, use single status queries:
$poller = new SessionStatusPoller($connector); $sessionStatus = $poller->poll($sessionId); if ($sessionStatus->isRunning()) { // Session still in progress — refresh QR code and poll again } elseif ($sessionStatus->isComplete()) { // Proceed to validation }
Or query the connector directly:
$sessionStatus = $connector->getSessionStatus($sessionId, timeoutMs: 1000);
Validating authentication response
It is critical to validate the authentication response to ensure the signature and certificate are trustworthy.
Setting up trusted CA certificates
use Sk\SmartId\Validation\TrustedCACertificateStore; // Create via SmartIdClient (OCSP revocation checking is enabled automatically) $validator = $client->createAuthenticationResponseValidator(); // PRODUCTION — load bundled certificates TrustedCACertificateStore::loadFromDefaults()->configureValidator($validator); // DEMO — load test certificates (upload certs to demo OCSP first, see "Uploading certificates to demo OCSP") // TrustedCACertificateStore::loadTestCertificates()->configureValidator($validator); // Custom directory // TrustedCACertificateStore::loadFromDirectory('/path/to/certs')->configureValidator($validator); // Manual certificates // $store = TrustedCACertificateStore::create() // ->addCertificate($pemEncodedCert) // ->addCertificateFromFile('/path/to/cert.pem.crt'); // $store->configureValidator($validator);
OCSP certificate revocation checking
OCSP revocation checking is enabled automatically when creating a validator via SmartIdClient::createAuthenticationResponseValidator(). The checker verifies that the end-entity certificate has not been revoked by reading the OCSP responder URL from the certificate's Authority Information Access (AIA) extension.
Note: For the demo environment, you must first upload your test certificates to the demo OCSP responder — see Uploading certificates to demo OCSP.
Validating device link authentication
use Sk\SmartId\Validation\TrustedCACertificateStore; use Sk\SmartId\Enum\CertificateLevel; use Sk\SmartId\Enum\SchemeName; use Sk\SmartId\DeviceLink\DeviceLinkInteraction; // Set up validator (demo environment — upload certs to demo OCSP first, see "Uploading certificates to demo OCSP") $validator = $client->createAuthenticationResponseValidator(); TrustedCACertificateStore::loadTestCertificates()->configureValidator($validator); // The interactions Base64 value must match what was sent in the original request $interactions = [DeviceLinkInteraction::displayTextAndPin('Log in to example.com')]; // Initiate the authentication session $session = $client->createDeviceLinkAuthentication() ->withRpChallenge($rpChallenge) ->withHashAlgorithm(HashAlgorithm::SHA512) ->withAllowedInteractionsOrder($interactions) ->initiate(); // The session now contains the pre-encoded interactions Base64 string // This ensures the same encoding is used for both API requests and signature verification $interactionsBase64 = $session->getInteractionsBase64(); // Validate and extract identity $identity = $validator->validate( $sessionStatus, $rpChallenge, // Base64-encoded RP challenge from the original request 'DEMO', // Relying Party name $interactionsBase64, // Base64-encoded interactions JSON from the session requiredCertificateLevel: CertificateLevel::QUALIFIED, schemeName: SchemeName::DEMO, // Use SchemeName::PRODUCTION for live );
The validator performs the following checks:
- Session state is
COMPLETEand result isOK - Signature protocol is
ACSP_V2 - Certificate level meets the requested level (if specified)
- Certificate is signed by a trusted CA and within its validity period
- Certificate basic constraints (not a CA certificate)
- Certificate policies contain Smart-ID scheme OIDs
- Certificate key usage includes
digitalSignature - Certificate Extended Key Usage includes Smart-ID authentication or
clientAuth signatureAlgorithmParametersvalidation (hashAlgorithm, saltLength, maskGenAlgorithm, trailerField)- RSA-PSS signature verification against the ACSP_V2 payload
Validating notification-based authentication
Validation is the same as device link. The only difference is how the session was initiated — the validation step uses the same AuthenticationResponseValidator::validate() method.
$identity = $validator->validate( $sessionStatus, $session->getRpChallenge(), 'DEMO', $session->getInteractionsBase64(), requiredCertificateLevel: CertificateLevel::QUALIFIED, schemeName: SchemeName::DEMO, );
Web2App flow validation
When using Web2App or App2App flows, the Smart-ID app redirects the user back to the Relying Party via the callback URL. The received callback URL will contain additional query parameters that must be validated.
Example callback URL for authentication:
https://rp.example.com/callback?value=RrKjjT4aggzu27YBddX1bQ&sessionSecretDigest=U4CKK13H1XFiyBofev9asqrzIrY5_Gszi_nL_zDKkBc&userChallengeVerifier=XtPfaGa8JnGtYrJjboooUf0KfY9sMEHrWFpSQrsUv9c
Validation steps:
- Verify that the
valuequery parameter matches the random value you included when creating the callback URL. - Verify
sessionSecretDigestmatchesBase64URL(SHA-256(Base64Decode(sessionSecret)))wheresessionSecretis from the session init response. - For authentication flows, verify
userChallengeVerifier— its SHA-256 hash (Base64URL-encoded) must matchsignature.userChallengefrom the session status response.
// 1. Verify session secret from callback URL $validator->verifySessionSecret( $sessionSecret, // from the original session response $sessionSecretDigest, // from the callback URL query parameter ); // 2. Verify user challenge from callback URL $validator->verifyUserChallenge( $userChallengeVerifier, // from the callback URL query parameter $userChallengeFromResponse, // from session status response signature.userChallenge ); // 3. Then proceed with standard validation — pass the callback URL for Web2App signature verification $identity = $validator->validate( $sessionStatus, $rpChallenge, 'DEMO', $interactionsBase64, $callbackUrl, // initialCallbackUrl used in Web2App flow requiredCertificateLevel: CertificateLevel::QUALIFIED, schemeName: SchemeName::DEMO, );
Validating callback URL with CallbackUrlUtil
The CallbackUrlUtil helper simplifies creating and validating callback URLs for Web2App/App2App flows.
Creating a callback URL with a random token
use Sk\SmartId\Util\CallbackUrlUtil; // Generate a callback URL with a cryptographically random token $result = CallbackUrlUtil::createCallbackUrl('https://your-app.com/callback'); $callbackUrl = $result['callbackUrl']; // e.g., https://your-app.com/callback?value=abc123... $token = $result['token']; // Store this to verify the callback later // Use the callback URL when initiating the session $session = $builder ->withCallbackUrl($callbackUrl) ->withAllowedInteractionsOrder([...]) ->initiate();
Validating session secret digest from callback
When the Smart-ID app redirects back to your callback URL, validate the sessionSecretDigest query parameter:
use Sk\SmartId\Util\CallbackUrlUtil; // Throws ValidationException if digest does not match CallbackUrlUtil::validateSessionSecretDigest( $sessionSecretDigest, // from the callback URL query parameter $sessionSecret, // from the original session init response );
Extracting user identity
After successful validation, the AuthenticationIdentity object provides:
$identity->getGivenName(); // e.g., 'QUALIFIED OK1' $identity->getSurname(); // e.g., 'TESTNUMBER' $identity->getFullName(); // e.g., 'QUALIFIED OK1 TESTNUMBER' $identity->getIdentityCode(); // e.g., '30303039914' $identity->getCountry(); // e.g., 'EE' // Additional methods for Baltic states (EE, LV, LT): $identity->getDateOfBirth(); // DateTimeImmutable or null $identity->getGender(); // 'M', 'F', or null $identity->getAge(); // int or null
The document number can be retrieved from the session result for future requests:
$documentNumber = $sessionStatus->getResult()->getDocumentNumber();
Additional request properties
Requesting IP address of user's device
For the IP address to be returned, the service provider (SK) must enable this option for your account. More info: https://sk-eid.github.io/smart-id-documentation/rp-api/3.0.3/request_properties.html
// Device link authentication with IP address sharing $session = $builder ->withCertificateLevel(CertificateLevel::QUALIFIED) ->withAllowedInteractionsOrder([ DeviceLinkInteraction::displayTextAndPin('Log in to example.com'), ]) ->withShareMdClientIpAddress() ->initiate(); // After session completes, retrieve the device IP address $sessionStatus = $poller->pollUntilComplete($session->getSessionId()); $deviceIpAddress = $sessionStatus->getDeviceIpAddress(); // IP address or null
The same withShareMdClientIpAddress() method is available on both DeviceLinkAuthenticationRequestBuilder and NotificationAuthenticationRequestBuilder.
Exception handling
The library provides specific exceptions for different error scenarios. All exceptions extend SmartIdException.
Permanent exceptions
These indicate client-side configuration or input errors.
InvalidParametersException— Invalid request parameters (HTTP 400).UnauthorizedException— Invalid RP credentials (HTTP 401) or user not found (HTTP 403).
User action exceptions
These cover scenarios where user actions or inactions lead to session termination.
UserRefusedException— User refused the operation.UserRefusedInteractionException— User pressed Cancel on a specific interaction screen. ExtendsUserRefusedException. UsegetInteraction()to see which interaction was canceled (fromresult.details).SessionTimeoutException— User did not respond within the allowed timeframe.WrongVerificationCodeException— User selected the wrong verification code.RequiredInteractionNotSupportedException— The required interaction type is not supported by the user's app.
User account exceptions
UserAccountException— Problems with the user's Smart-ID account.isNoSuitableAccount()— No suitable account found (HTTP 471).isPersonShouldViewApp()— User should view Smart-ID app or self-service portal (HTTP 472).isClientTooOld()— Client-side API too old (HTTP 480).
DocumentUnusableException— The requested document cannot be used.
Protocol and server exceptions
ProtocolFailureException— An error occurred in the signing protocol.ServerErrorException— Technical error occurred at the Smart-ID server side.
Validation exceptions
ValidationException— Thrown during response validation: certificate trust, signature mismatch, etc.
Technical exceptions
TechnicalErrorException— Thrown for technical errors during processing (e.g., malformed responses, unexpected data formats).
Server-side exceptions
SmartIdException— Smart-ID service temporarily unavailable (HTTP 5xx) or other unclassified errors.UnderMaintenanceException— System is under maintenance (HTTP 580). Retry the request later.SessionNotFoundException— Session not found (HTTP 404).
Example of handling exceptions
use Sk\SmartId\Exception\SmartIdException; use Sk\SmartId\Exception\UserRefusedException; use Sk\SmartId\Exception\UserRefusedInteractionException; use Sk\SmartId\Exception\SessionTimeoutException; use Sk\SmartId\Exception\WrongVerificationCodeException; use Sk\SmartId\Exception\UserAccountException; use Sk\SmartId\Exception\DocumentUnusableException; use Sk\SmartId\Exception\RequiredInteractionNotSupportedException; use Sk\SmartId\Exception\ProtocolFailureException; use Sk\SmartId\Exception\ServerErrorException; use Sk\SmartId\Exception\ValidationException; use Sk\SmartId\Exception\UnderMaintenanceException; try { $sessionStatus = $poller->pollUntilComplete($sessionId); $identity = $validator->validate($sessionStatus, ...); } catch (SessionTimeoutException $e) { // User did not respond in time } catch (UserRefusedInteractionException $e) { // User refused a specific interaction — check which one $interaction = $e->getInteraction(); // e.g., 'displayTextAndPIN' } catch (UserRefusedException $e) { // User refused the operation (covers USER_REFUSED and all USER_REFUSED_* variants) } catch (WrongVerificationCodeException $e) { // User selected wrong verification code } catch (DocumentUnusableException $e) { // Document is unusable for this operation } catch (RequiredInteractionNotSupportedException $e) { // Required interaction not supported by user's app } catch (UserAccountException $e) { if ($e->isNoSuitableAccount()) { // No suitable Smart-ID account } elseif ($e->isPersonShouldViewApp()) { // User should check Smart-ID app } elseif ($e->isClientTooOld()) { // Client-side API too old } } catch (ProtocolFailureException $e) { // Protocol failure — retry may help } catch (ServerErrorException $e) { // Smart-ID server error — retry later } catch (ValidationException $e) { // Authentication response validation failed — do not trust! } catch (UnderMaintenanceException $e) { // System is under maintenance (HTTP 580) — retry later } catch (SmartIdException $e) { // General Smart-ID error (includes ACCOUNT_UNUSABLE, EXPECTED_LINKED_SESSION, HTTP 5xx) }
Logging
The SDK supports optional PSR-3 logging. Pass any LoggerInterface implementation (e.g. Monolog) to SmartIdClient and AuthenticationResponseValidator. If no logger is provided, a NullLogger is used and no output is produced.
What is logged
| Component | Level | What |
|---|---|---|
SmartIdRestConnector |
debug |
Outgoing HTTP request method and URL |
SmartIdRestConnector |
debug |
Successful response status code |
SmartIdRestConnector |
warning |
Error response status code |
SessionStatusPoller |
debug |
Each poll attempt (session ID) |
SessionStatusPoller |
info |
Session completed (session ID, end result) |
SessionStatusPoller |
warning |
Authentication failed (end result: TIMEOUT, USER_REFUSED, etc.) |
SessionStatusPoller |
warning |
Polling timed out (session ID, attempts) |
| Request builders | info |
Session initiation |
| Request builders | debug |
Session initiated (session ID) |
AuthenticationResponseValidator |
info |
Validation start and success |
AuthenticationResponseValidator |
error |
Validation failed (error message) |
AuthenticationResponseValidator |
debug |
Each validation step (trust chain, constraints, policies, signature) |
OcspCertificateRevocationChecker |
debug |
OCSP request/response and success |
OcspCertificateRevocationChecker |
warning |
OCSP check failure |
Note: The SDK never logs request/response bodies, personal data, or secrets. Only metadata such as URLs, status codes, and session IDs are logged.
Using with SmartIdClient
The logger is passed as the last constructor parameter and automatically propagated to the connector, session status poller, and request builders.
use Psr\Log\LoggerInterface; use Sk\SmartId\SmartIdClient; use Sk\SmartId\Ssl\SslPinnedPublicKeyStore; // Example with Monolog $logger = new \Monolog\Logger('smart-id'); $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stderr', \Monolog\Level::Debug)); $client = new SmartIdClient( relyingPartyUUID: '00000000-0000-4000-8000-000000000000', relyingPartyName: 'DEMO', hostUrl: 'https://sid.demo.sk.ee/smart-id-rp/v3', sslPinnedKeys: SslPinnedPublicKeyStore::loadDemo(), logger: $logger, );