This library supports Smart-ID API v3.1.
- Smart-ID Java client
- How to use it with RP API v3.1
- Test accounts for testing
- Logging
- Setting up SmartIdClient for v3.1
- Device link flows
- Querying sessions status
- Sessions status response
- Example of querying session status in v3.1
- Validating sessions status response
- Certificate by document number
- Linked signature session flow
- Notification-based flows
- Exception handling
- Network connection configuration of the client
The Smart-ID Java client can be used for easy integration of the Smart-ID solution to information systems or e-services. This library supports Smart-ID API v3.1.
- user authentication
- obtain user's signing certificate
- creating digital signature
- Java 17 or 21
You can use the library as a Maven dependency from the Maven Central.
<dependency>
<groupId>ee.sk.smartid</groupId>
<artifactId>smart-id-java-client</artifactId>
<version>INSERT_VERSION_HERE</version>
</dependency>implementation 'ee.sk.smartid:smart-id-java-client:INSERT_VERSION_HERE'
Changes introduced with new library versions are described in CHANGELOG.md
Support for Smart-ID API v3.1 has been added to the library. The code for v3.1 is located under the ee.sk.smartid package. This version introduces new device link and notification-based flows for authentication, certificate choice and signing.
NB! v2 API classes are removed.
To use the v3.1 API, import the relevant classes from the ee.sk.smartid package.
import ee.sk.smartid.SmartIdConnector;To log requests going to Smart-ID API set ee.sk.smartid.rest.LoggingFilter to log at trace level. For applications on Spring Boot this can be done by adding following line to application.yml:
logging.level.ee.sk.smartid.rest.LoggingFilter: trace
Configure to use with Smart-ID Demo environment NB! Smart-ID Basic level accounts (certificate level ADVANCED) are not supported for DEMO
Live SSL certificates of Smart-ID service provider (SK) can be found here: https://sk-eid.github.io/smart-id-documentation/https_pinning.html#_rp_api_smart_id_com_certificates Demo SSL certificates can be found here: https://sk-eid.github.io/smart-id-documentation/https_pinning.html#_sid_demo_sk_ee_certificates
Recommended way is to use truststore and provide it to the client.
// Read truststore containing Smart-ID service provider (SK) SSL certificates
InputStream is = SmartIdClient.class.getResourceAsStream("demo_server_trusted_ssl_certs.jks");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(is, "changeit".toCharArray());
// Initialize SmartIdClient and set connection parameters.
var smartIdClient = new SmartIdClient();
// set relying party details
client.setRelyingPartyUUID("00000000-0000-4000-8000-000000000000");
client.setRelyingPartyName("DEMO");
// set Smart-ID API host URL
client.setHostUrl("https://sid.demo.sk.ee/smart-id-rp/v3/");
// set the trust store containing SK SSL certificates
client.setTrustStore(trustStore);Also it is possible to add trusted certificates one by one.
// Initialize SmartIdClient and set connection parameters.
var smartIdClient = new SmartIdClient();
// set relying party details
client.setRelyingPartyUUID("00000000-0000-4000-8000-000000000000");
client.setRelyingPartyName("DEMO");
// set Smart-ID API host URL
client.setHostUrl("https://sid.demo.sk.ee/smart-id-rp/v3/");
// add trusted SSL certificates
client.setTrustedCertificates("-----BEGIN CERTIFICATE-----\nMIIFIjCCBAqgAwIBAgIQBH3ZvDVJl5qtCPwQJSruuj...");Device-link flows are more secure way to make sure user that started the authentication or signing is in control of the device or in the proximity of the device. More info available here https://sk-eid.github.io/smart-id-documentation/rp-api/device_link_flows.html
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 are ADVANCED or QUALIFIED. Defaults to QUALIFIED.signatureProtocol: Required. Signature protocol to use. Currently, the only allowed value is ACSP_V2.signatureProtocolParameters: Required. Parameters for the ACSP_V2 signature protocol.rpChallenge: Required. Base64-encoded value, length between 44 and 88 characters.signatureAlgorithm: Required. Signature algorithm name. Supported value onlyrsassa-pss.signatureAlgorithmParameters: Required. Parameters for the signature algorithm.hashAlgorithm: Required. Hash algorithm name. Supported values areSHA-256,SHA-384,SHA-512,SHA3-256,SHA3-384,SHA3-512.
interactions: Required. Base64-encoded JSON string of an array of interaction objects.- Each interaction object includes:
type: Required. Type of interaction. Allowed types aredisplayTextAndPIN,confirmationMessage.displayText60ordisplayText200: Required based on type. Text to display to the user.displayText60is limited to 60 characters, anddisplayText200is limited to 200 characters.
- Each interaction object includes:
requestProperties: requestProperties:shareMdClientIpAddress: Optional. Boolean indicating whether to request the IP address of the user's device.
capabilities: Optional. Array of strings specifying capabilities. Used only when agreed with the Smart-ID provider.initialCallbackUrl: Optional. Must match regex^https:\/\/([^\\|]+)$. If it contains the vertical bar|, it must be percent-encoded. Should be set when using same device flows.
sessionID: A string that can be used to request the session status result.sessionToken: Unique random value that will be used to connect this signature attempt between the relevant parties (RP, RP-API, mobile app).sessionSecret: Base64-encoded random key value that should be kept secret and shared only between the RP backend and the RP-API server.deviceLinkBase: Required base URI used to form device link or QR code.
Anonymous authentication is a new feature in Smart-ID API v3.1. It allows to authenticate users without knowing their identity. RP can learn the user's identity only after the user has authenticated themselves.
// For security reasons a new hash value must be created for each new authentication request
String rpChallenge = RpChallengeGenerator.generate();
// Store generated rpChallenge only on backend side. Do not expose it to the client side.
// Used for validating authentication sessions status OK response
// Set up builder
DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient
.createDeviceLinkAuthentication()
// to use anonymous authentication, do not set semantics identifier or document number
.withRpChallenge(rpChallenge)
.withInteractions(Collections.singletonList(
DeviceLinkInteraction.displayTextAndPin("Logging into <app-name>") // Display text should be concise and specific.
));
// Initiate authentication session
DeviceLinkSessionResponse authenticationSessionResponse = builder.initAuthenticationSession();
// Get authentication session request used for starting the authentication session and use it later to validate sessions status response
AuthenticationSessionRequest authenticationSessionRequest = builder.getAuthenticationSessionRequest();
// Use sessionID to start polling for session status
String sessionId = authenticationSessionResponse.sessionID();
// Following values are used for generating device link or QR-code
String sessionToken = authenticationSessionResponse.sessionToken();
// Store sessionSecret only on backend side. Do not expose it to the client side.
String sessionSecret = authenticationSessionResponse.sessionSecret();
URI deviceLinkBase = authenticationSessionResponse.deviceLinkBase();
// Will be used to calculate elapsed time being used in QR-code
Instant responseReceivedAt = authenticationSessionResponse.receivedAt();
// Next steps:
// - Generate QR-code or device link to be displayed to the user
// - Start querying sessions statusJump to Generate QR-code and device link to see how to generate QR-code or device link from the response. Jump to Query session status for an example of session querying.
More info about Semantics Identifier can be found here
SemanticsIdentifier semanticsIdentifier = new SemanticsIdentifier(
// 3 character identity type
// (PAS-passport, IDC-national identity card or PNO - (national) personal number)
SemanticsIdentifier.IdentityType.PNO,
SemanticsIdentifier.CountryCode.EE, // 2 character ISO 3166-1 alpha-2 country code
"30303039914"); // identifier (according to country and identity type reference)
// For security reasons a new rpChallenge must be created for each new authentication request
RpChallenge rpChallenge = RpChallengeGenerator.generate();
// Store generated rpChallenge only backend side. Do not expose it to the client side.
// Used for validating authentication sessions status OK response
DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient
.createDeviceLinkAuthentication()
.withSemanticsIdentifier(semanticsIdentifier)
.withRpChallenge(rpChallenge.toBase64EncodedValue())
.withInteractions(Collections.singletonList(
DeviceLinkInteraction.displayTextAndPin("Logging into <app-name>") // Display text should be concise and specific.
));
// Initiate authentication session
DeviceLinkSessionResponse authenticationSessionResponse = builder.initAuthenticationSession();
// Get authentication session request used for starting the authentication session and use it later to validate sessions status response
AuthenticationSessionRequest authenticationSessionRequest = builder.getAuthenticationSessionRequest();
// Use sessionID to start polling for session status
String sessionId = authenticationSessionResponse.sessionID();
// Following values are used for generating device link or QR-code
String sessionToken = authenticationSessionResponse.sessionToken();
// Store sessionSecret only on backend side. Do not expose it to the client side.
String sessionSecret = authenticationSessionResponse.sessionSecret();
URI deviceLinkBase = authenticationSessionResponse.deviceLinkBase();
// Will be used to calculate elapsed time being used in QR-code
Instant responseReceivedAt = authenticationSessionResponse.receivedAt();
// Next steps:
// - Generate QR-code or device link to be displayed to the user
// - Start querying sessions statusJump to Generate QR-code and device link to see how to generate QR-code or device link from the response. Jump to Query session status for an example of session querying.
String documentNumber = "PNOLT-40504040001-MOCK-Q";
// For security reasons a new rpChallenge must be created for each new authentication request
RpChallenge rpChallenge = RpChallengeGenerator.generate();
// Store generated rpChallenge only on backend side. Do not expose it to the client side.
// Used for validating OK authentication sessions status response
DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient
.createDeviceLinkAuthentication()
.withDocumentNumber(documentNumber)
.withRpChallenge(rpChallenge.toBase64EncodedValue())
.withInteractions(Collections.singletonList(
DeviceLinkInteraction.displayTextAndPin("Logging into <app-name>") // Display text should be concise and specific.
));
// Initiate authentication session
DeviceLinkSessionResponse authenticationSessionResponse = builder.initAuthenticationSession();
// Get authentication session request used for starting the authentication session and use it later to validate sessions status response
AuthenticationSessionRequest authenticationSessionRequest = builder.getAuthenticationSessionRequest();
// Use sessionID to start polling for session status
String sessionId = authenticationSessionResponse.sessionID();
// Following values are used for generating device link or QR-code
String sessionToken = authenticationSessionResponse.sessionToken();
// Store sessionSecret only on backend side. Do not expose it to the client side.
String sessionSecret = authenticationSessionResponse.sessionSecret();
URI deviceLinkBase = authenticationSessionResponse.deviceLinkBase();
// Will be used to calculate elapsed time being used in QR-code
Instant responseReceivedAt = authenticationSessionResponse.receivedAt();
// Next steps:
// - Generate QR-code or device link to be displayed to the user
// - Start querying sessions statusJump to Generate QR-code and device link to see how to generate QR-code or device link from the response. Jump to Query session status for an example of session querying.
String documentNumber = "PNOLT-40504040001-MOCK-Q";
// For security reasons a new rpChallenge must be created for each new authentication request
RpChallenge rpChallenge = RpChallengeGenerator.generate();
// Store generated rpChallenge only on backend side. Do not expose it to the client side.
// Used for validating OK authentication sessions status response
// Generate callback URL to be used for same device flows(Web2App, App2App)
CallbackUrl callbackUrl = CallbackUrlUtil.createCallbackUrl("your-app://callback");
DeviceLinkAuthenticationSessionRequestBuilder builder = smartIdClient
.createDeviceLinkAuthentication()
.withDocumentNumber(documentNumber)
.withRpChallenge(rpChallenge.toBase64EncodedValue())
.withInteractions(Collections.singletonList(
DeviceLinkInteraction.displayTextAndPin("Logging into <app-name>") // Display text should be concise and specific.
))
.withInitialCallbackUrl(callbackUrl.initialCallbackUri().toString()); // Set initial callback URL in the session request
// Initiate authentication session
DeviceLinkSessionResponse authenticationSessionResponse = builder.initAuthenticationSession();
// Get authentication session request used for starting the authentication session and use it later to validate sessions status response
AuthenticationSessionRequest authenticationSessionRequest = builder.getAuthenticationSessionRequest();
// Use sessionID to start polling for session status
String sessionId = authenticationSessionResponse.sessionID();
// Following values are used for generating device link or QR-code
String sessionToken = authenticationSessionResponse.sessionToken();
// Store sessionSecret only on backend side. Do not expose it to the client side.
String sessionSecret = authenticationSessionResponse.sessionSecret();
URI deviceLinkBase = authenticationSessionResponse.deviceLinkBase();
// Will be used to calculate elapsed time being used in QR-code
Instant responseReceivedAt = authenticationSessionResponse.receivedAt();
// Next steps:
// - Generate QR-code or device link to be displayed to the user
// - Start querying sessions statusJump to Generate QR-code and device link to see how to generate QR-code or device link from the response. Jump to Query session status for an example of session querying. Jump to Validate callback URL for more info about validating callback URL.
The request parameters for the device-link signature session are as follows:
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 are ADVANCED, QUALIFIED or QSCD. Defaults to QUALIFIED.signatureProtocol: Required. Signature protocol to use. Currently, the only allowed value is RAW_DIGEST_SIGNATURE.signatureProtocolParameters: Required. Parameters for the RAW_DIGEST_SIGNATURE signature protocol.digest: Required. Base64 encoded digest to be signed.signatureAlgorithm: Required. Signature algorithm name. Only supported value isrsassa-psssignatureAlgorithmParameters: Required. Parameters for the signature algorithm.hashAlgorithm: Required. Hash algorithm name. Supported values areSHA-256,SHA-384,SHA-512,SHA3-256,SHA3-384,SHA3-512.
interactions: Required. Base64-encoded JSON string of an array of interaction objects.- Each interaction object includes:
type: Required. Type of interaction. Allowed types aredisplayTextAndPIN,confirmationMessage.displayText60ordisplayText200: Required based on type. Text to display to the user.displayText60is limited to 60 characters, anddisplayText200is limited to 200 characters.
- Each interaction object includes:
initialCallbackUrl: Optional. Must match regex^https:\/\/([^\\|]+)$. If it contains a |, it must be percent-encoded. Should be used for same-device flow.nonce: Optional. Random string, up to 30 characters. If present, must have at least 1 character.requestProperties:shareMdClientIpAddress: Optional. Boolean indicating whether to request the IP address of the user's device.
capabilities: Optional. Array of strings specifying capabilities. Used only when agreed with the Smart-ID provider.
The response from a successful device-link signature session creation contains the following parameters:
sessionID: A string that can be used to request the session status result.sessionToken: Unique random value that will be used to connect this signature attempt between the relevant parties (RP, RP-API, mobile app).sessionSecret: Base64-encoded random key value that should be kept secret and shared only between the RP backend and the RP-API server.deviceLinkBase: Required. Base URI used to form the device link or QR code.
// Create the signable data
var signableData = new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_256);
// Create the Semantics Identifier
var semanticsIdentifier = new SemanticsIdentifier(
SemanticsIdentifier.IdentityType.PNO,
SemanticsIdentifier.CountryCode.EE,
"40504040001"
);
// Initiate the device-link signature
DeviceLinkSessionResponse signatureResponse = client.createDeviceLinkSignature()
.withCertificateLevel(CertificateLevel.QSCD)
.withSignableData(signableData)
.withSemanticsIdentifier(semanticsIdentifier)
.withHashAlgorithm(HashAlgorithm.SHA_512)
.withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Please sign the <document-name>"))) // Display text should be concise and specific.
.withInitialCallbackUrl("https://example.com/callback") // Only needed for same-device flows(Web2App, App2App)
.initSignatureSession();
// Process the signature response
String sessionID = signatureResponse.sessionID();
String sessionToken = signatureResponse.sessionToken();
// Store sessionSecret only on backend side. Do not expose it to the client side.
String sessionSecret = signatureResponse.sessionSecret();
Instant receivedAt = signatureResponse.receivedAt();
String deviceLinkBase = signatureResponse.deviceLinkBase();
// Generate QR-code or device link to be displayed to the user
// Start querying sessions statusJump to Generate QR-code and device link to see how to generate QR-code or device link from the response. Jump to Query session status for an example of session querying.
// Create the signable data
var signableData = new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_256);
// Specify the document number
String documentNumber = "PNOEE-40504040001-MOCK-Q";
// Build the device-link signature request
DeviceLinkSessionResponse signatureResponse = smartIdClient.createDeviceLinkSignature()
.withCertificateLevel(CertificateLevel.QSCD)
.withSignableData(signableData)
.withDocumentNumber(documentNumber)
.withHashAlgorithm(HashAlgorithm.SHA_512)
.withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Please sign the <document-name>"))) // Display text should be concise and specific.
.initSignatureSession();
// Process the signature response
String sessionID = signatureResponse.sessionID();
String sessionToken = signatureResponse.sessionToken();
// Store sessionSecret only on backend side. Do not expose it to the client side.
String sessionSecret = signatureResponse.sessionSecret();
Instant receivedAt = signatureResponse.receivedAt();
String deviceLinkBase = signatureResponse.deviceLinkBase();
// Generate QR-code or device link to be displayed to the user
// Start querying sessions statusJump to Generate QR-code and device link to see how to generate QR-code or device link from the response. Jump to Query session status for an example of session querying.
Handle exceptions appropriately. The Java client provides specific exceptions for different error scenarios, such as UserAccountNotFoundException, UserRefusedException and others.
try {
DeviceLinkSessionResponse response = builder.init*Session();
} catch (UserAccountNotFoundException e) {
System.out.println("User account not found.");
} catch (RelyingPartyAccountConfigurationException e) {
System.out.println("Relying party account configuration issue.");
} catch (RequiredInteractionNotSupportedByAppException e) {
System.out.println("The required interaction is not supported by the user's app.");
} catch (ServerMaintenanceException e) {
System.out.println("Server maintenance in progress, please try again later.");
} catch (SmartIdClientException e) {
System.out.println("An error occurred: " + e.getMessage());
}For the IP to be returned the service provider (SK) must switch on this option. More info available at https://sk-eid.github.io/smart-id-documentation/rp-api/3.0.3/request_properties.html#ip_sharing
Authentication is used for an example, shareMdClientIpAddress can also be used with certificate choice and signature sessions request by using method withShareMdClientIpAddress(true).
DeviceLinkSessionResponse authenticationSessionResponse = client
.createDeviceLinkAuthentication()
.withRpChallenge(rpChallenge)
.withCertificateLevel(AuthenticationCertificateLevel.QUALIFIED) // Certificate level can either be "QUALIFIED" or "ADVANCED"
.withInteractions(Collections.singletonList(
DeviceLinkInteraction.displayTextAndPin("Logging into <app-name>") // Display text should be concise and specific.
))
// setting property to request the IP-address of the user's device
.withShareMdClientIpAddress(true)
.initAuthenticationSession();An app can support different interaction types, and a Relying Party can specify the 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.
Below are examples of interaction elements specifically for device link flows:
Example 1: confirmationMessage with fallback to displayTextAndPIN
Description: The RP's first choice is confirmationMessage; if not available, then fall back to displayTextAndPIN.
builder.withInteractions(List.of(
DeviceLinkInteraction.confirmationMessage("Up to 200 characters of text here.."),
DeviceLinkInteraction.displayTextAndPin("Up to 60 characters of text here..")
))Example 2: confirmationMessage Only (No Fallback)
Description: Insist on confirmationMessage;
NB! If interactions is not supported the process will fail if fallback is not provided.
builder.withInteractions(List.of(
DeviceLinkInteraction.confirmationMessage("Up to 200 characters of text here..")
));Documentation to device link and QR-code requirements https://sk-eid.github.io/smart-id-documentation/rp-api/device_link_flows.html
To use the Smart-ID demo environment, you must specify smart-id-demo as schemeName.
See: https://sk-eid.github.io/smart-id-documentation/environments.html#_demo
Device link can be generated for 3 use cases: QR-code, web link to Smart-ID app, app link to Smart-ID app.
schemeName: Controls which Smart-ID environment is targeted. Default value issmart-id.deviceLinkBase: Value ofdeviceLinkBasereturned in session-init response.version: Version of the device link. Only allowed value is"1.0".deviceLinkType: Type of the device link. Possible values areQR,Web2App,App2App.sessionType: Type of the sessions the device link is for. Possible values areauth,sign,cert.sessionToken: Token from the session response.elapsedSeconds: Seconds since the session-init response was received – only forQR_CODElang: User language. Default value iseng. Is used to set language of the fallback page. Fallback page is used for cases when the app is not installed or some other problem occurs with opening a device linkdigest: Base64-encoded digest or rpChallenge from session-init. Required forauthandsignflows.relyingPartyNameBase64: Base64-encoded relying party name, used for authentication sessions. It is used to calculate the authCode.interactions: Base64-encoded JSON string of an array of interaction objects, used to calculate the authCode.initialCallbackUrl: Optional. Initial callback URL used for the same device(Web2App or App2App) device link flows. It must match the regex^https:\/\/([^\\|]+)$. If it contains the vertical bar|, it must be percent-encoded.
import ee.sk.smartid.rest.dao.DeviceLinkAuthenticationSessionRequest;
DeviceLinkSessionResponse sessionResponse; // response from the session initiation query.
DeviceLinkAuthenticationSessionRequest sessionRequest; // request used for starting the authentication or signing session. For example authentication session request is used.
// Calculate elapsed seconds since session response
long elapsedSeconds = Duration.between(session.receivedAt(), Instant.now()).getSeconds();
// Build final device link URI with authCode
URI deviceLink = smartIdClient.createDynamicContent()
.withDeviceLinkBase(sessionResponse.deviceLinkBase())
.withDeviceLinkType(DeviceLinkType.QR_CODE)
.withSessionType(SessionType.AUTHENTICATION)
.withSessionToken(sessionResponse.sessionToken())
.withElapsedSeconds(elapsedSeconds)
.withLang("eng")
.withDigest("YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=")
.withInteractions(sessionRequest.interactions()) // interactions from the authentication or signing session request, should be empty when used with device link certificate choice session
.buildDeviceLink(sessionResponse.sessionSecret());DeviceLinkSessionResponse sessionResponse; // response from the session initiation query.
DeviceLinkAuthenticationSessionRequest sessionRequest; // request used for starting the authentication or signing session. For example authentication session request is used.
// Build final device link URI with authCode
URI deviceLink = new DeviceLinkBuilder()
.withSchemeName("smart-id-demo") // override default scheme name to use demo environment
.withDeviceLinkBase(sessionResponse.deviceLinkBase())
.withDeviceLinkType(DeviceLinkType.APP_2_APP)
.withSessionType(SessionType.AUTHENTICATION)
.withSessionToken(sessionResponse.sessionToken())
.withLang("est") // override language
.withDigest("YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=")
.withInteractions(sessionRequest.interactions()) // interactions from the authentication or signing session request, should be empty when used with device link certificate choice session
.withInitialCallbackUrl("https://your-app/callback")
.buildDeviceLink(sessionResponse.sessionSecret());Creating a QR code uses the Zxing library to generate a QR code image with device link as content. According to link size the QR-code of version 9 (53x53 modules) is used. For the QR-code to be scannable by most devices the QR code module size should be ~10px. It is achieved by setting the height and width of the QR code to 610px (calculated as (53+2x4)*10px). Generated QR code will have error correction level low.
DeviceLinkSessionResponse sessionResponse; // response from the session initiation query.
DeviceLinkAuthenticationSessionRequest sessionRequest; // request used for starting the authentication or signing session. For example authentication session request is used.
// Calculate elapsed seconds from response received time
long elapsedSeconds = Duration.between(response.receivedAt(), Instant.now()).getSeconds();
// Build final device link URI with authCode
URI deviceLink = new DeviceLinkBuilder()
.withDeviceLinkBase(sessionResponse.deviceLinkBase())
.withDeviceLinkType(DeviceLinkType.QR_CODE)
.withSessionType(SessionType.AUTHENTICATION)
.withSessionToken(sessionResponse.sessionToken())
.withLang("est") // override language
.withDigest("YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=")
.withElapsedSeconds(elapsedSeconds)
.withInteractions(sessionRequest.interactions()) // interactions from the authentication or signing session request, should be empty when used with device link certificate choice session
.buildDeviceLink(sessionResponse.sessionSecret());
// Generate QR code image from device link URI
String qrCodeDataUri = QrCodeGenerator.generateDataUri(deviceLink.toString());
// Return Data URI to frontend and display the QR-codeNotably, the module size in pixels should be more than 5px and less than 20px. The recommended module size is 10px. QR code version 9 (53x53 modules) is automatically selected by content size
Other image size in range 366px to 1159px is also possible. Width and height of 366px produce a QR code with a module size of 6px. The width and height of 1159px produce a QR code with a module size of 19px.
DeviceLinkSessionResponse sessionResponse; // response from the session initiation query.
DeviceLinkAuthenticationSessionRequest sessionRequest; // request used for starting the authentication or signing session. For example authentication session request is used.
// Calculate elapsed seconds from response received time
long elapsedSeconds = Duration.between(response.receivedAt(), Instant.now()).getSeconds();
// Build final device link URI with authCode
URI deviceLink = new DeviceLinkBuilder()
.withDeviceLinkBase(sessionResponse.deviceLinkBase())
.withDeviceLinkType(DeviceLinkType.QR_CODE)
.withSessionType(SessionType.AUTHENTICATION)
.withSessionToken(sessionResponse.sessionToken())
.withLang("est") // override language
.withDigest("YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=")
.withElapsedSeconds(elapsedSeconds)
.withInteractions(sessionRequest.interactions()) // interactions from the authentication or signing session request, should be empty when used with device link certificate choice session
.buildDeviceLink(sessionResponse.sessionSecret());
// Create QR-code with height and width of 570px and quiet area of 2 modules.
BufferedImage qrCodeBufferedImage = QrCodeGenerator.generateImage(deviceLink.toString(), 570, 570, 2);
// Return Data URI to frontend and display the QR-code
String qrCodeDataUri = QrCodeGenerator.convertToDataUri(qrCodeBufferedImage, "png");When using same device flows (Web2App or App2App) the initialCallbackUrl will be used by the Smart-ID app to redirect the user back to the Relying Party application. Received callback URL will contain additional query parameters that must be validated by the Relying Party.
Example of received callback URL for authentication:
https://rp.example.com/callback-url?value=RrKjjT4aggzu27YBddX1bQ&sessionSecretDigest=U4CKK13H1XFiyBofev9asqrzIrY5_Gszi_nL_zDKkBc&userChallengeVerifier=XtPfaGa8JnGtYrJjboooUf0KfY9sMEHrWFpSQrsUv9c
Example of received callback URL for signature or certificate choice
https://rp.example.com/callback-url?value=RrKjjT4aggzu27YBddX1bQ&sessionSecretDigest=U4CKK13H1XFiyBofev9asqrzIrY5_Gszi_nL_zDKkBc
- RP must verify that the user sessions has
callbackUrl.urlToken()with same value as in query parametervalue. - RP must verify that the
sessionSecretDigestquery parameter matches the calculated digest created from session secret received in device link session init response. For this library providesCallbackUrlUtil.validateSessionSecretDigest(digestFromCallbackUrl, sessionSecret) - For authentication same device flow RP also must verify the
userChallengeVerifierquery parameter. This can be done when polling the session status has finished and session status response has to be validated.deviceLinkAuthenticationResponseValidator.validate(sessionStatus, authenticationSessionRequest, userChallengeVerifier, schemaName, brokeredRpName);Value to validateuserChallengeVerifieris in the session status responsesignature.userChallenge.
The Smart-ID v3.1 API includes new session status request path for retrieving session results. Session status request is to be used for device link-based and notification-based flows.
The session status response includes various fields depending on whether the session has completed or is still running. Below are the key fields returned in the response:
state: RUNNING or COMPLETEresult.endResult: Outcome of the session (e.g., OK, USER_REFUSED, TIMEOUT)result.documentNumber: Document number returned whenendResultisOK. Can be used in further signature and authentication requests to target the same device.result.details: Contains additional info when user refused interactionsignatureProtocol: Either ACSP_V2 (for authentication) or RAW_DIGEST_SIGNATURE (for signature)signature: Contains the following fields based on the signatureProtocol used:- For
ACSP_V2: value, serverRandom, userChallenge, flowType, signatureAlgorithm, signatureAlgorithmParameters, - For
RAW_DIGEST_SIGNATURE: value, flowType, signatureAlgorithm, signatureAlgorithmParameters
- For
cert: Includes certificate information with value (Base64-encoded certificate) and certificateLevel (ADVANCED or QUALIFIED).ignoredProperties: Any unsupported or ignored properties from the request.interactionTypeUsed: The interaction type used for the session.deviceIpAddress: IP address of the mobile device, if requested.
The following example shows how to use the SessionStatusPoller to fetch the session status until it's complete.
*SessionResponse sessionResponse;
// Get the session status poller
SessionsStatusPoller poller = client.getSessionsStatusPoller();
// Get sessionID from current session response
SessionStatus sessionStatus = poller.fetchFinalSessionStatus(sessionResponse.sessionID());
// Session can have two states RUNNING or COMPLETED, check sessionStatus.getResult().getEndResult() for OK or error responses (f.e USER_REFUSED, TIMEOUT)
if("COMPLETE".equalsIgnoreCase(sessionStatus.getState())){
System.out.println("Session completed with result: "+sessionStatus.getResult().getEndResult());
}The following example shows how to use the SessionStatusPoller to only query the sessions status single time. NB! If using this method for device link-based flows. Make sure the pollingSleepTimeout is not set or does not impact generating the QR-code for every second.
*SessionResponse sessionResponse;
// Get the session status poller
SessionStatusPoller poller = client.getSessionStatusPoller();
// Querying the sessions status
SessionStatus sessionStatus = poller.getSessionStatus(sessionResponse.sessionID());
// Checking sessions state
if ("RUNNING".equalsIgnoreCase(sessionStatus.getState())) {
// Session is still running and querying can be continued
// Dynamic content can be generated and displayed to the user
} else if ("COMPLETE".equalsIgnoreCase(sessionStatus.getState())){
// continue to validate the sessions status
} else {
throw UnprocessableSmartIdResponseException("Invalid session state was returned");
}It's important to validate the session status response to ensure that the returned signature or authentication result is valid.
For validating authentication session status response, use the AuthenticationResponseValidator.
For validating signature session status response, use the SignatureResponseValidator.
NB! Integrators must validate signature value against expected signature value.
CertificateValidator will check if the certificate is not expired and is trusted by constructing certificate chain with trust anchors and intermediate CA certificates provided in the TrustedCACertStore. Will be used by AuthenticationResponseValidator and SignatureResponseValidator.
// Set up TrustedCACertStore
// Option 1 - initialize certificate store with default locations for trust anchor truststore and for intermediate CA certificates
TrustedCACertStore trustedCACertStore = new FileTrustedCAStoreBuilder().build();
// Option 2 - initialize certificate store with custom locations for trust anchor truststore and for intermediate CA certificates
TrustedCACertStore trustedCACertStore = new FileTrustedCAStoreBuilder()
.withTrustAnchorTruststorePath("path/to/trustAnchorTruststore.jks")
.withTrustAnchorTruststorePassword("password")
.withIntermediateCAStorePath("path/to/intermediateCAStore.jks")
.withIntermediateCAStorePassword("password")
.build();
// Option 3 - Provide trust anchors and intermediate CA certificates directly
Set<TrustAnchor> trustAnchors;
List<X509Certificate> intermediateCACertificates;
TrustedCACertStore trustedCACertStore = new DefaultTrustedCACertStore()
.withTrustAnchors(trustAnchors)
.withIntermediateCACertificates(intermediateCACertificates)
.build();
// Set up CertificateValidator with the trusted CA store
CertificateValidator certificateValidator = new CertificateValidatorImpl(trustedCACertStore);DeviceLinkAuthenticationResponseValidator depends on CertificateValidator. Checkout setting up CertificateValidator
// Set up AuthenticationResponseValidator with the CertificateValidator
DeviceLinkAuthenticationResponseValidator deviceLinkAuthenticationResponseValidator = new AuthenticationResponseValidator(certificateValidator);
// Create authentication request builder
DeviceLinkAuthenticationSessionRequestBuilder authenticationRequestBuilder = smartIdClient.createDeviceLinkAuthentication()...;
// Initialize session
DeviceLinkSessionResponse sessionResponse = authenticationRequestBuilder.initAuthenticationSession();
// Get request used for starting the authentication session and use it later to validate sessions status response
DeviceLinkAuthenticationSessionRequest authenticationSessionRequest = authenticationRequestBuilder.getAuthenticationSessionRequest();
// get sessions result
SessionStatusPoller poller = smartIdClient.getSessionStatusPoller();
SessionStatus sessionStatus = poller.fetchFinalSessionStatus(sessionResponse.sessionID());
// validate sessions state is completed
if("COMPLETE".equals(sessionStatus.getState())){
// validate the session status response with authentication session request and return authentication identity
AuthenticationIdentity authenticationIdentity = deviceLinkAuthenticationResponseValidator.validate(sessionStatus, authenticationSessionRequest, "smart-id-demo");
}NotificationAuthenticationResponseValidator depends on CertificateValidator. Checkout setting up CertificateValidator
// Set up AuthenticationResponseValidator with the CertificateValidator
NotificationAuthenticationResponseValidator notificationAuthenticationResponseValidator = new AuthenticationResponseValidator(certificateValidator);
// Create authentication request builder
NotificationAuthenticationSessionRequestBuilder authenticationRequestBuilder = smartIdClient.createDeviceLinkAuthentication()...;
// Initialize session
NotificationAuthenticationSessionResponse sessionResponse = authenticationRequestBuilder.initAuthenticationSession();
// Get request used for starting the authentication session and use it later to validate sessions status response
NotificationAuthenticationSessionRequest authenticationSessionRequest = authenticationRequestBuilder.getAuthenticationSessionRequest();
// get sessions result
SessionStatusPoller poller = smartIdClient.getSessionStatusPoller();
SessionStatus sessionStatus = poller.fetchFinalSessionStatus(sessionResponse.sessionID());
// validate sessions state is completed
if("COMPLETE".equals(sessionStatus.getState())){
// validate the session status response with authentication session request and return authentication identity
AuthenticationIdentity authenticationIdentity = notificationAuthenticationResponseValidator.validate(sessionStatus, authenticationSessionRequest, "smart-id-demo");
}CertificateChoiceResponseValidator depends on CertificateValidator. Checkout setting up CertificateValidator
try {
// Set up CertificateChoiceResponseValidator with the CertificateValidator
CertificateChoiceResponseValidator certificateChoiceResponseValidator = new CertificateChoiceResponseValidator(certificateValidator);
// Validate and map the session status. If the sessions end result is other than OK, then an exception will be thrown.
CertificateChoiceResponse certificateChoiceResponse = certificateChoiceResponseValidator.validate(sessionStatus);
} catch (UserRefusedException e) {
System.out.println("User refused the session.");
} catch (SessionTimeoutException e) {
System.out.println("Session timed out.");
} catch (DocumentUnusableException e) {
System.out.println("Document is unusable for the session.");
} catch (SmartIdClientException e) {
System.out.println("An unexpected error occurred: " + e.getMessage());
}SignatureResponseValidator depends on CertificateValidator. Checkout setting up CertificateValidator
try {
// Objects needed for validation
CertificateResponse certResponse; // queried by document number or use CertificateChoiceResponse
SignableData signableData; // data that was sent for signing
// Initialize the signature response validator with CertificateValidator
SignatureResponseValidator signatureResponseValidator = new SignatureResponseValidator(certificateValidator);
// Validate and map the session status. If the sessions end result is other than OK, then an exception will be thrown.
SignatureResponse signatureResponse = signatureResponseValidator.validate(signatureSessionStatus, CertificateLevel.QUALIFIED.name());
// Validate signature value. This step can be skipped if other means of validating the signature value can be used.
SignatureValueValidator signatureValueValidator = new SignatureValueValidatorImpl();
signatureValueValidator.validate(signatureResponse.getSignatureValue(),
signableData.calculateHash(),
certResponse.certificate(),
signatureResponse.getRsaSsaPssParameters());
// Process the response (e.g., save to database or pass to another system)
handleSignatureResponse(signatureResponse);
} catch (UserRefusedException e) {
System.out.println("User refused the session.");
} catch (SessionTimeoutException e) {
System.out.println("Session timed out.");
} catch (DocumentUnusableException e) {
System.out.println("Document is unusable for the session.");
} catch (SmartIdClientException e) {
System.out.println("An unexpected error occurred: " + e.getMessage());
}The session status response may return various error codes indicating the outcome of the session. Below are the possible end result values for a completed session:
OK: Session completed successfully.USER_REFUSED: User refused the session.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.USER_REFUSED_CERT_CHOICE: User has multiple accounts and pressed Cancel on device choice screen.USER_REFUSED_INTERACTION: User pressed Cancel on the interaction screen.interactionfield in the result details contains info which interaction was canceled.displayTextAndPIN- User pressed Cancel on PIN screen during displayTextAndPIN flow.confirmationMessage- User cancelled on confirmationMessage screen.confirmationMessageAndVerificationCodeChoice- User cancelled on confirmationMessageAndVerificationCodeChoice screen.
PROTOCOL_FAILURE: An error occurred in the signing protocol.EXPECTED_LINKED_SESSION: RP has configured signature session that should follow device-link certificate choice session incorrectly and the process cannot be completed.SERVER_ERROR- Technical error occurred at the server side and the process was terminated.
In API v3.1 new endpoint was introduced to simplify querying certificate for signing. RP can directly query the user's signing certificate by document number — no session flow or user interaction required. Can be used for device link and notification-based signature flows. Only requirement is that the device link authentication is successfully completed before to get the document number.
The request parameters for the certificate by document number request are as follows:
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 areADVANCED,QUALIFIEDorQSCD. Defaults toQUALIFIED.
state: Required. Indicates result. Possible values:OK: Certificate found and returned.DOCUMENT_UNUSABLE: user's Smart-ID account is not usable for signing
cert: Required. Object containing the signing certificate.value: Required. Base64-encoded X.509 certificate (matches pattern^[a-zA-Z0-9+/]+={0,2}$)certificateLevel: Required. Level of the certificate, Possible valuesADVANCEDorQUALIFIED
String documentNumber = "PNOLT-40504040001-MOCK-Q";
// Build the certificate by document number request and query the certificate
CertificateByDocumentNumberResult certResponse = smartIdClient
.createCertificateByDocumentNumber()
.withDocumentNumber(documentNumber)
.getCertificateByDocumentNumber();
// Set up the certificate validator
TrustedCACertStore trustedCACertStore = new FileTrustedCAStoreBuilder().build();
CertificateValidator certificateValidator = new CertificateValidatorImpl(trustedCACertStore);
// Validate the certificate
certificateValidator.validateCertificate(certResponse.certificate());Checkout out other ways to set up TrustedCaCertStore with CertificateValidator in Set up CertificateValidator.
In API v3.1 a new flow was introduced to link signature session to a previously completed certificate choice session. The flow starts off with device link certificate choice session and must be followed by a linked notification-based signature session.
Anonymous device link certificate choice session can be initiated without knowing the user's document number. When the session is completed successfully, the Smart-ID API will stay waiting for the RP to start the linked notification-based signature session.
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. ADVANCED/QUALIFIED/QSCD, defaults to QUALIFIED.nonce: Random string, up to 30 characters. If present, must have at least 1 character. Used for overriding idempotent behaviour.capabilities: Used only when agreed with Smart-ID provider. When omitted, request capabilities are derived from certificateLevel.requestProperties: A request properties object as a set of name/value pairs. For example, requesting the IP address of the user's device.initialCallbackUrl: Optional. Must match regex^https:\/\/([^\\|]+)$. If it contains the vertical bar|, it must be percent-encoded. Should be used for same-device flow.
sessionID: A string that can be used to request the session status result.sessionToken: Unique random value that will be used to connect created session between the relevant parties (RP, RP-API, mobile app).sessionSecret: Base64-encoded random key value that should be kept secret and shared only between the RP backend and the RP-API server.deviceLinkBase: Required. Base URI used to form the device link or QR code.
DeviceLinkSessionResponse certificateChoice = client.createDeviceLinkCertificateRequest()
.withCertificateLevel(CertificateLevel.QUALIFIED)
.withInitialCallbackUrl("https://example.com/callback") // Only needed for same-device flows(Web2App, App2App)
.initiateCertificateChoice();
String sessionId = certificateChoice.sessionID();
// SessionID is used to query sessions status later
String sessionToken = certificateChoice.sessionToken();
// Store sessionSecret only on backend side. Do not expose it to the client side.
String sessionSecret = certificateChoice.sessionSecret();
String deviceLinkBase = certificateChoice.deviceLinkBase();
Instant responseReceivedAt = certificateChoice.receivedAt();Jump to Generate QR-code and device link to see how to generate QR-code or device link from the response. Jump to Query session status for an example of session querying.
Second part of the linked signature flow. Will be used to start the signature session after the device link certificate choice session is completed successfully.
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 are ADVANCED, QUALIFIED or QSCD. Defaults to QUALIFIED.signatureProtocol: Required. Signature protocol to use. Currently, the only allowed value is RAW_DIGEST_SIGNATURE.signatureProtocolParameters: Required. Parameters for the RAW_DIGEST_SIGNATURE signature protocol.digest: Required. Base64 encoded digest to be signed.signatureAlgorithm: Required. Signature algorithm name. Only supported value isrsassa-psssignatureAlgorithmParameters: Required. Parameters for the signature algorithm.hashAlgorithm: Required. Hash algorithm name. Supported values areSHA-256,SHA-384,SHA-512,SHA3-256,SHA3-384,SHA3-512.
linkedSessionID: Required. Session ID of the previously completed certificate choice session.interactions: Required. Base64-encoded JSON string of an array of interaction objects.- Each interaction object includes:
type: Required. Type of interaction. Allowed types aredisplayTextAndPIN,confirmationMessage.displayText60ordisplayText200: Required based on type. Text to display to the user.displayText60is limited to 60 characters, anddisplayText200is limited to 200 characters.
- Each interaction object includes:
nonce: Optional. Random string, up to 30 characters. If present, must have at least 1 character.requestProperties:shareMdClientIpAddress: Optional. Boolean indicating whether to request the IP address of the user's device.
capabilities: Optional. Array of strings specifying capabilities. Used only when agreed with the Smart-ID provider.
sessionID: Required. String that can be used to request the signature session status result.
// Prerequisite: device link certificate choice has been completed successfully.
DeviceLinkSessionResponse certificateChoiceSessionResponse;
CertificateChoiceResponse certificateChoiceResponse;
// Start the linked notification signature session using the sessionID from the certificate choice session
LinkedSignatureSessionResponse signatureSessionResponse = smartIdClient.createLinkedNotificationSignature()
.withDocumentNumber(certificateChoiceResponse.getDocumentNumber())
.withLinkedSessionID(certificateChoiceSessionResponse.sessionID())
.withSignableData(new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_256))
.withInteractions(List.of(DeviceLinkInteraction.displayTextAndPin("Please sign the <document-name>"))) // Display text should be concise and specific.
.initSignatureSession();
// SessionID is used to query sessions status later
String sessionId = signatureSessionResponse.sessionID();Jump to Query session status for an example of session querying.
Notification-Based flow- Push notifications: The user gets a notification directly on their Smart-ID app to proceed with the signing or authentication process.
- Known users or devices:
- Notification-based flows are more vulnerable to phishing attacks. It is recommended to use notification-based flows after the user has been identified by using device link-based flows.
- No dynamic updates: The process is straightforward, with no need to update links or use QR codes.
Device Link flow- Device links: Generates links for QR codes or Web2App/App2App links that the user interacts with to start the process.
- Anonymous authentication: the user's details are not required beforehand. RP validates the user after the Smart-ID authentication is completed.
- Real-time updates: QR-code needs to be refreshed every second to ensure validity.
Linked flow- Combination of anonymous certificate choice and notification-based signing: Starts with a device link-based certificate choice session followed by a notification-based signing session.
- QR-code or device link will be used only for the certificate choice part of the flow.
- Supports only device link-based interactions in the signature part of the flow.
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 are ADVANCED, QUALIFIED or QSCD. Defaults to QUALIFIED.signatureProtocol: Required. Signature protocol to use. Currently, the only allowed value is ACSP_V2.signatureProtocolParameters: Required. Parameters for the ACSP_V2 signature protocol.rpChallenge: Required. Random value with size in range of 32-64 bytes. Must be Base64 encoded.signatureAlgorithm: Required. Signature algorithm name. Supported values is 'rsassa-pss'signatureAlgorithmParameters: Required. Parameters for the signature algorithm.hashAlgorithm: Required. Hash algorithm name. Supported values areSHA-256,SHA-384,SHA-512,SHA3-256,SHA3-384,SHA3-512.
interactions: Required. An array of interaction objects defining the interactions in order of preference.- Each interaction object includes:
type: Required. Type of interaction. Allowed types aredisplayTextAndPIN,confirmationMessage,confirmationMessageAndVerificationCodeChoice.displayText60ordisplayText200: Required based on type. Text to display to the user.displayText60is limited to 60 characters, anddisplayText200is limited to 200 characters.
- Each interaction object includes:
requestProperties: requestProperties:shareMdClientIpAddress: Optional. Boolean indicating whether to request the IP address of the user's device.
capabilities: Optional. Array of strings specifying capabilities. Used only when agreed with the Smart-ID provider.vcType: Required. Type of verification code to be used. Currently, the only allowed value isnumeric4.
sessionID: Required. String used to request the operation result.
String documentNumber = "PNOLT-40504040001-MOCK-Q";
// For security reasons a rpChallenge must be created for each new authentication request
RpChallenge rpChallenge = RpChallengeGenerator.generate();
// Store generated rpChallenge only on backend side. Do not expose it to the client side.
// Used for validating authentication sessions status OK response
// Generate verification code and display it to the user for confirmation
String verificationCode = VerificationCodeCalculator.calculate(rpChallenge.value());
NotificationAuthenticationSessionResponse authenticationSessionResponse = client
.createNotificationAuthentication()
.withDocumentNumber(documentNumber)
.withRpChallenge(rpChallenge.toBase64EncodedValue())
.withCertificateLevel(AuthenticationCertificateLevel.QUALIFIED)
.withInteractions(Collections.singletonList(
NotificationInteraction.displayTextAndPin("Logging into <app-name>") // Display text should be concise and specific.
))
.initAuthenticationSession();
// SessionID is used to query sessions status later
String sessionId = authenticationSessionResponse.sessionID();Jump to Query session status for an example of session querying.
SemanticsIdentifier semanticsIdentifier = new SemanticsIdentifier(
SemanticsIdentifier.IdentityType.PNO,
SemanticsIdentifier.CountryCode.EE,
"40504040001"
);
// For security reasons a rpChallenge must be created for each new authentication request
RpChallenge rpChallenge = RpChallengeGenerator.generate();
// Store generated rpChallenge only on backend side. Do not expose it to the client side.
// Used for validating authentication sessions status OK response
// Generate verification code and display it to the user for confirmation
String verificationCode = VerificationCodeCalculator.calculate(rpChallenge.value());
NotificationAuthenticationSessionResponse authenticationSessionResponse = client
.createNotificationAuthentication()
.withSemanticsIdentifier(semanticsIdentifier)
.withRpChallenge(rpChallenge.toBase64EncodedValue())
.withCertificateLevel(AuthenticationCertificateLevel.QUALIFIED)
.withInteractions(Collections.singletonList(
NotificationInteraction.displayTextAndPin("Logging into <app-name>"))) // Display text should be concise and specific.
.initAuthenticationSession();
// SessionID can be used to query sessions status later
String sessionId = authenticationSessionResponse.sessionID();Jump to Query session status for an example of session querying.
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. ADVANCED/QUALIFIED/QSCD, defaults to QUALIFIED.nonce: Random string, up to 30 characters. If present, must have at least 1 character.capabilities: Used only when agreed with Smart-ID provider. When omitted, request capabilities are derived from certificateLevel.requestProperties: A request properties object as a set of name/value pairs. For example, requesting the IP address of the user's device.
sessionID: A string that can be used to request the session status result.
SemanticsIdentifier semanticsIdentifier = new SemanticsIdentifier(
// 3 character identity type
// (PAS-passport, IDC-national identity card or PNO - (national) personal number)
SemanticsIdentifier.IdentityType.PNO,
SemanticsIdentifier.CountryCode.EE, // 2 character ISO 3166-1 alpha-2 country code
"40504040001"); // identifier (according to country and identity type reference)
// Use requested certificate level to validate certificate choice session status OK response.
CertificateLevel requestedCertificateLevel = CertificateLevel.QSCD; // Certificate level can either be "QUALIFIED", "ADVANCED" or "QSCD"
NotificationCertificateChoiceSessionResponse certificateChoiceSessionResponse = client
.createNotificationCertificateChoice()
.withSemanticsIdentifier(semanticsIdentifier)
.withCertificateLevel(requestedCertificateLevel)
.initCertificateChoice();
String sessionId = certificateChoiceSessionResponse.sessionID();
// SessionID is used to query sessions status laterJump to Query session status for an example of session querying.
The request parameters for the notification-based signature session are as follows:
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 are ADVANCED, QUALIFIED or QSCD. Defaults to QUALIFIED.signatureProtocol: Required. Signature protocol to use. Currently, the only allowed value is RAW_DIGEST_SIGNATURE.signatureProtocolParameters: Required. Parameters for the RAW_DIGEST_SIGNATURE signature protocol.digest: Required. Base64 encoded digest to be signed.signatureAlgorithm: Required. Signature algorithm name. Onlyrsassa-pssis currently supported.signatureAlgorithmParameters: Required. Parameters for the signature algorithm.hashAlgorithm: Required. Hash algorithm used for digest. Supported values areSHA-256,SHA-384,SHA-512,SHA3-256,SHA3-384,SHA3-512.
interactions: Required. Base64-encoded string of interactions to be used for a session. The interactions are defined in order of preference.- Each interaction object includes:
type: Required. Type of interaction. Allowed types aredisplayTextAndPIN,confirmationMessage,confirmationMessageAndVerificationCodeChoice.displayText60ordisplayText200: Required based on type. Text to display to the user.displayText60is limited to 60 characters, anddisplayText200is limited to 200 characters.
- Each interaction object includes:
nonce: Optional. Random string, up to 30 characters. If present, must have at least 1 character. To be used for overriding idempotency.requestProperties: requestProperties:shareMdClientIpAddress: Optional. Boolean indicating whether to request the IP address of the user's device.
capabilities: Optional. Array of strings specifying capabilities. Used only when agreed with the Smart-ID provider.
sessionID: Required. String used to request the operation result.vc: Required. Object describing the verification code details.type: Required. Type of the verification code. Currently, the only allowed type isnumeric4.value: Required. Value of the verification code to be displayed to the user.
// Create the signable data
var signableData = new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_256);
// Create the Semantics Identifier
SemanticsIdentifier semanticsIdentifier = new SemanticsIdentifier(
SemanticsIdentifier.IdentityType.PNO,
SemanticsIdentifier.CountryCode.EE,
"40504040001"
);
// Build the notification signature request
NotificationSignatureSessionResponse signatureSessionResponse = smartIdClient.createNotificationSignature()
.withCertificateLevel(CertificateLevel.QSCD)
.withSignableData(signableData)
.withSemanticsIdentifier(semanticsIdentifier)
.withInteractions(List.of(
NotificationInteraction.confirmationMessage("Please sign the <document-name>")) // Display text should be concise and specific.
)
.initSignatureSession();
// Get the session ID and continue to querying session status
String sessionID = signatureSessionResponse.sessionID();
// Display verification code to the user
String verificationCode = signatureSessionResponse.vc().getValue();Jump to Query session status for an example of session querying.
// Create the signable data
var signableData = new SignableData("dataToSign".getBytes(), HashAlgorithm.SHA_256);
// Specify the document number
String documentNumber = "PNOEE-40504040001-MOCK-Q";
// Initiate the session
NotificationSignatureSessionResponse signatureResponse = client.createNotificationSignature()
.withRelyingPartyUUID(client.getRelyingPartyUUID())
.withRelyingPartyName(client.getRelyingPartyName())
.withCertificateLevel(CertificateLevel.QUALIFIED)
.withSignableData(signableData)
.withDocumentNumber(documentNumber)
.withAllowedInteractionsOrder(List.of(
NotificationInteraction.confirmationMessage("Please sign the <document-name>"))) // Display text should be concise and specific.
.initSignatureSession();
// Get the session ID and continue to querying session status
String sessionID = signatureResponse.sessionID();
// Display verification code to the user
String verificationCode = signatureResponse.vc().getValue();Jump to Query session status for an example of session querying.
Handle exceptions appropriately. The Java client provides specific exceptions for different error scenarios, such as:
UserAccountNotFoundExceptionRelyingPartyAccountConfigurationExceptionSessionNotFoundExceptionRequiredInteractionNotSupportedByAppExceptionServerMaintenanceExceptionSmartIdClientException
try {
NotificationSignatureSessionResponse response = builder.initSignatureSession();
} catch (UserAccountNotFoundException e) {
System.out.println("User account not found.");
} catch (RelyingPartyAccountConfigurationException e) {
System.out.println("Relying party account configuration issue.");
} catch (RequiredInteractionNotSupportedByAppException e) {
System.out.println("The required interaction is not supported by the user's app.");
} catch (ServerMaintenanceException e) {
System.out.println("Server maintenance in progress, please try again later.");
} catch (SmartIdClientException e) {
System.out.println("An error occurred: " + e.getMessage());
}Idempotent behaviour means that if the session request with same values is made multiple times within a 15-second window, the same response with identical values will be returned. If there is a need to override this behaviour, a nonce can be used. Nonce value must be a random string with a minimum length of 1 and a maximum length of 30 characters.
Notification-based signature request is used as an example. Nonce can also be used with other signing session request
(device-link signature and certificate choice; notification-based certificate choice) by using method withNonce("randomValue").
NotificationSignatureSessionResponse signatureSessionResponse = smartIdClient.createNotificationSignature()
.withRelyingPartyUUID(smartIdClient.getRelyingPartyUUID())
.withRelyingPartyName(smartIdClient.getRelyingPartyName())
.withCertificateLevel(CertificateLevel.QUALIFIED)
.withSignableData(signableData)
.withSemanticsIdentifier(semanticsIdentifier)
.withInteractions(Collections.singletonList(
NotificationInteraction.confirmationMessage("Please sign the <document-name>") // Display text should be concise and specific.
))
// if request is made again in 15 seconds, the idempotent behaviour applies and same response with same values will be returned
// set nonce to override idempotent behaviour
.withNonce("randomValue")
.initSignatureSession();For the IP to be returned the service provider (SK) must switch on this option. More info available at https://sk-eid.github.io/smart-id-documentation/rp-api/3.0.3/request_properties.html#ip_sharing
Authentication is used for an example, shareMdClientIpAddress can also be used with certificate choice and signature sessions request by using method withShareMdClientIpAddress(true).
NotificationAuthenticationSessionResponse authenticationSessionResponse = client
.createNotificationAuthentication()
.withDocumentNumber(documentNumber)
.withRpChallenge(rpChallenge.toBase64EncodedValue())
.withCertificateLevel(AuthenticationCertificateLevel.QUALIFIED)
.withInteractions(Collections.singletonList(
NotificationInteraction.displayTextAndPin("Logging into <app-name>") // Display text should be concise and specific.
))
// setting property to request the IP-address of the user's device
.withShareMdClientIpAddress(true)
.initAuthenticationSession();An app can support different interaction types, and a Relying Party can specify the preferred interactions with or without fallback options. Different interactions can support different amounts of data to display information to the user.
Below are examples of interactions.
Example 1: confirmationMessageAndVerificationCodeChoice with fallback to confirmationMessage and with fallback to displayTextAndPIN
Description: The RP's first choice is confirmationMessageAndVerificationCodeChoice; The second choice is confirmationMessage; The third choice is displayTextAndPIN.
builder.withInteractions(List.of(
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
Description: Use confirmationMessageAndVerificationCodeChoice interaction exclusively.
NB! Process will fail when interaction is not supported and there is no fallback
builder.withInteractions(List.of(
NotificationInteraction.confirmationMessageAndVerificationCodeChoice("Up to 200 characters of text here...")
));The Smart-ID Java client library provides specific exceptions for different error scenarios. Handle exceptions appropriately to provide a good user experience.
Exception Categories
- Permanent Exceptions
These exceptions indicate issues that are unlikely to be resolved by retrying the request. They are typically caused by client misconfiguration or invalid data input
SmartIdClientExceptionThrown for general client-side errors, such as:- Missing or invalid configuration (e.g.,
trustSslContextnot set).
- Missing or invalid configuration (e.g.,
SmartIdRequestSetupExceptionThrown when the request field validations fails, such as:- Missing required fields (e.g.,
relyingPartyUUID,relyingPartyName,signatureProtocol). - Invalid values for fields (e.g.
interactionTypecontaining duplicate types).
- Missing required fields (e.g.,
- Unprocessable Response Exceptions
These exceptions are thrown when the response from the Smart-ID service cannot be processed, typically due to malformed data or protocol violations.
UnprocessableSmartIdResponseException: Thrown when the response from the Smart-ID service cannot be processed.- Missing required fields (e.g.,
state,endResult,signatureAlgorithm). - Incorrectly encoded Base64 strings in signature or certificate.
- Unexpected or unsupported
signatureProtocol.
- Missing required fields (e.g.,
- User Action Exceptions
These exceptions cover scenarios where user actions or inactions lead to session termination or errors.
UserRefusedExceptionBase exception for user refusal scenarios.SessionTimeoutException: User did not respond within the allowed timeframe.UserSelectedWrongVerificationCodeExceptionThrown when the user selects an incorrect verification code during the process.
- User Account Exceptions
These exceptions handle issues related to the user's Smart-ID account or session requirements.
CertificateLevelMismatchExceptionThrown when the returned certificate level does not meet the requested level.DocumentUnusableExceptionIndicates that the requested document cannot be used for the operation.UserAccountUnusableExceptionThrown when the user's Smart-ID account is not currently usable for the requested operation.
- Validation and Parsing Exceptions
These exceptions arise during validation or parsing operations within the library.
CertificateParsingExceptionThrown when the X.509 certificate cannot be parsed.SignatureValidationExceptionThrown when signature validation fails due to mismatched algorithms or corrupted data.
- Server side exceptions
ProtocolFailureExceptionThrown when the Smart-ID API received invalid data such (f.e wrong data in generate device link)ExpectedLinkedSessionExceptionThrown when the Relying Party did not configure linked signature session to follow anonymous device-link certificate choice session.SmartIdServerExceptionThrown when the Smart-ID terminates the process due to a server-side error.
Under the hood each operation (authentication, choosing certificate and signing) consist of 2 request steps:
- Initiation request
- Session status request
Session status request by default is a long poll method, meaning the request method might not return until a timeout expires. Caller can tune each poll's timeout value in milliseconds inside the bounds set by service operator to turn it into a short poll.
SmartIdClient client = new SmartIdClient();
// ...
// sets the timeout for each session status poll
client.setSessionStatusResponseSocketOpenTime(TimeUnit.SECONDS, 5L);
// sets the pause between each session status poll
client.setPollingSleepTimeout(TimeUnit.SECONDS, 1L); As Smart-ID Java client uses Jersey client for network communication underneath, we've exposed Jersey API for network connection configuration.
Here's an example how to configure HTTP connector's custom socket timeouts for the Smart-ID client:
SmartIdClient client = new SmartIdClient();
// ...
ClientConfig clientConfig = new ClientConfig();
clientConfig.property(ClientProperties.CONNECT_TIMEOUT, 5000);
clientConfig.property(ClientProperties.READ_TIMEOUT, 30000);
client.setNetworkConnectionConfig(clientConfig);And here's an example how to use Apache Http Client with custom socket timeouts as the HTTP connector instead of the default HttpUrlConnection:
SmartIdClient client = new SmartIdClient();
// ...
ClientConfig clientConfig = new ClientConfig().connectorProvider(new ApacheConnectorProvider());
RequestConfig reqConfig = RequestConfig.custom()
.setConnectTimeout(5000)
.setSocketTimeout(30000)
.setConnectionRequestTimeout(5000)
.build();
clientConfig.property(ApacheClientProperties.REQUEST_CONFIG, reqConfig);
client.setNetworkConnectionConfig(clientConfig);Keep in mind that the HTTP connector timeout of waiting for data shouldn't normally be less than the timeout for session status poll.
ResteasyClient resteasyClient = new ResteasyClientBuilder()
.sslContext(SmartIdClient.createSslContext(Arrays.asList(
"pem cert 1", "pem cert 2")))
.build();
SmartIdClient client = new SmartIdClient();
client.setRelyingPartyUUID("00000000-0000-4000-8000-000000000000");
client.setRelyingPartyName("DEMO");
client.setHostUrl("https://sid.demo.sk.ee/smart-id-rp/v3/");
client.setConfiguredClient(resteasyClient);