Verifying a User’s Identity from Your Application Using Samsung Wallet’s App2App SDK
-
Similar Topics
-
By Samsung Newsroom
Samsung Electronics today announced the expansion of its commercial display offerings, led by the global launch of Samsung Spatial Signage, at Integrated Systems Europe (ISE) 2026 in Barcelona. The announcement includes new AI-powered content capabilities through Samsung VXT, new additions to Samsung’s supersized commercial display lineup and expanded enterprise collaboration with Cisco-certified wide-format display solutions.
“For commercial environments, bringing displays and content solutions together is becoming increasingly important,” said SW Yong, President and Head of the Visual Display (VD) Business at Samsung Electronics. “Glasses-free 3D Spatial Signage, combined with new AI-powered capabilities in Samsung VXT, allows us to deliver a more integrated approach to immersive commercial displays, helping businesses create engaging experiences across a wide range of commercial environments.”
Bringing Brands to Life Across a Range of Environments With Spatial Signage
Spatial Signage is Samsung’s industry-leading 3D digital signage that delivers an immersive visual experience. Using Samsung’s patented 3D Plate technology, it creates a sense of spatial depth positioned behind the LCD panel. Content retains the sharpness of 2D visuals while adding natural-looking 3D depth — without the need for specialized equipment such as 3D glasses. The display’s presentation naturally draws attention in retail, luxury, museum and entertainment settings, helping direct focus to key promotions, exhibitions or important information.
The newly launched 85-inch Spatial Signage display features a 4K UHD resolution (2,160 x 3,840) in a 9:16 portrait format, which enables brands and venues to present 360-degree rotating visuals that show front, back and side views of a product or scene.
Powered by Samsung’s industry-leading Quantum Processor, the display provides 4K UHD upscaling, 16-bit color mapping and dynamic HDR refinement to deliver sharper detail, smoother tonal transitions and consistent color accuracy. Additionally, an anti-glare panel helps maintain clarity under bright or challenging lighting conditions.
Spatial Signage features an UltraThin Design with a slim 52mm profile and a lightweight 49kg build. Compatible with a Slim Fit Wall Mount, the display installs like conventional signage and integrates cleanly into compact or design-sensitive locations, without the bulky, box-like enclosures typically associated with traditional showcase-style displays.1 Spatial Signage is available globally in an 85-inch model, with the launch of 32-inch and 55-inch sizes to follow.
AI Studio, a new AI-powered content app within Samsung VXT,2 was showcased at ISE 2026 to demonstrate streamlined content creation for all Samsung signage connected to the platform. The app transforms static images into signage-ready video without the need for external tools or manual setup. Content created through VXT’s AI Studio app is automatically optimized with refined shadow detailing, adjusted margins and background treatments for Spatial Signage — creating more realistic and balanced visuals tailored for a wide variety of commercial environments.
Recognized for its pioneering 3D capabilities, Spatial Signage has been named a CES 2026 Innovation Award Honoree in the newly introduced Enterprise Tech category, which made Samsung one of the first to be recognized in the category during its commercial debut at the show. Last year, the display was also named an IFA 2025 Innovation Award Honoree in the ‘Best in Emerging Tech’ category.
Redefining Supersized Signage for Bold Business Impact
Samsung is reinforcing its leadership in supersized commercial displays with a growing lineup of extra-large formats built for high-impact business environments. At ISE 2026, Samsung introduced the 130-inch Micro RGB signage (QPHX model) to commercial audiences for the first time. Previously unveiled at CES 2026 for the ultra-premium home entertainment market, the display features Samsung’s most advanced Micro LED technology to date. It combines micro-scale RGB LEDs with the Micro RGB AI Engine Pro to deliver vivid color expression and exceptional picture quality in an ultra-slim design, making it ideal for flagship retail and premium spaces.
Also unveiled at ISE 2026 was the 108-inch The Wall All-in-One (MMF-A model) in 2K resolution, engineered to dramatically simplify large-format LED deployment. Like previous models (146-inch 4K and 2K, 136-inch 2K and 110-inch 2K), it reduces on-site setup time and labor compared to traditional LED walls. Installation is possible in as little as two hours, depending on display size.3 However, the new 108-inch model features a more compact, split-panel design that makes supersized LED installation as efficient as mounting two LCD screens rather than a full LED wall — all at a much faster pace.
Together with the previously introduced 105-inch QPDX-5K and 115-inch QHFX models, the addition of the 130-inch Micro RGB Signage and The Wall All-in-One series gives businesses more ways to create immersive, ultra-large visual experiences across lobbies, showrooms, boardrooms and other high-impact commercial spaces. This expanded lineup reinforces Samsung’s 17-year leadership in the global digital signage market.4
Advancing Enterprise Collaboration With Cisco and Logitech Partnerships
Samsung’s 115-inch 4K Smart Signage (QHFX model) and 146-inch 2K The Wall All-in-One (IAB model) lead the industry in advanced supersized displays, offering seamless, immersive meeting spaces without the complexity of multi-screen setups. These supersized models are the latest Samsung displays to be certified for compatibility with Cisco’s collaboration devices, joining the previously certified Samsung QMC lineup. Notably, Samsung The Wall All-in-One is the world’s first LED display to receive the certification.5
Cisco certification follows a rigorous testing program to confirm the reliability of the display’s video interfaces and ensure optimized image quality for video meetings. It also confirms the visibility of displays within Cisco’s Control Hub management platform and verifies secure, seamless integration across meeting spaces. Together, these factors contribute to high-quality meeting experiences for participants and improved enterprise management for IT teams.
Additionally, through a new partnership with Logitech, Samsung 4K Smart Signage QBC series is now included in Microsoft’s Express Install for Microsoft Teams Rooms, enabling fast, cost-effective meeting room setups. The offering combines Samsung displays with Logitech’s certified Microsoft Teams Rooms conferencing solution to simplify room installations, allowing them to be completed in under an hour.
Slim Fit Wall Mount must be purchased separately. ︎ Samsung VXT is a cloud-based digital signage platform that combines content creation, management and remote device control within a single CMS, accessible via desktop and mobile. VXT is sold separately, and feature availability may vary by region. AI Studio will be available globally in 1H 2026 and may incur additional costs depending on usage. ︎ The Wall All-in-One 110-inch model requires three preset modules. The 146-inch model requires four preset modules. Install time estimate based on internal testing. ︎ Consumer TVs are excluded. Source: Omdia Q3 2025 Public Display Report ︎ Cisco Collaboration Devices ︎ View the full article
-
By Samsung Newsroom
In a previous blog article, we learned how we could utilize Samsung Wallet's RP SDK in order to verify a user's identity from an Android application. In this tutorial, we learn how to verify a user's identity directly from a website using Samsung Wallet and its Web2App API in a Spring Boot web server.
Prerequisites
The process described has the following prerequisites:
A valid US driver's license or state ID for the user whose identity is to be verified The Samsung Galaxy device used needs to be registered for the US region and have mDL support Complete the Samsung Wallet Partner onboarding process Create a Wallet Card template with the Relying Party type in the Samsung Wallet Partners Portal Implementing the Verify with Wallet functionality in your website
The Verify with Wallet (VWW) process utilizing the Web2App method consists of two distinct parts.
The "Verify with Samsung Wallet" button. This button contains the VWW link with the RP card data tokenized as the CData. The user can click this VWW link to initiate the verification process. The partner server containing the /key and /auth endpoints. The partner server processes the requests sent from the Samsung Wallet application and handles the complete VWW process. Frontend configuration
In order to initiate the Verify with Wallet process, we need to implement a "Verify with Samsung Wallet" button in a webpage.
Implementing the button is a very simple process similar to creating a traditional "Add to Wallet" button. We can make use of the data transmit link approach and create a button that contains the VWW link: https://a.swallet.link/vww/v1/{cardId}#Clip?cdata={cdata}
Replace {cardId} with the ID of your own card. Meanwhile, the CData value needs to be generated in real time. This is done using a process similar to generating CData for ATW operation, only with the payload contained being different according to the specification for the Relying Party card type. Check out the sample code for the complete process of CData generation and using it in the button.
Backend configuration
Once the button implementation is complete, you need to configure your server to handle the exchange of information between your server and Samsung Wallet application. The VWW process requires the partner to define 2 API endpoints:
/rp/v1.0/{cardId}/{refId}/key: Establishes a secure session and prepares the request data for the process. /rp/v1.0/{cardId}/{refId}/auth: Processes encrypted authentication data and mDL data received from the Wallet application. The workflow for the information exchange is as follows:
Once the VWW button is clicked, the Samsung Wallet application opens. Samsung Wallet checks if the device has a driver's license already enrolled in the device. If an mDL already exists on the device, the Samsung Wallet application calls the /key endpoint to establish a session. After establishing session with the partner server and retrieving the mDoc request, the Samsung Wallet application prompts the user to confirm if they wish to share their information with the partner. After the user confirms that they wish to proceed, the application finally sends the requested information to the /auth API to complete the VWW process. Define the /key endpoint
When the user clicks the "Verify with Samsung Wallet" button, the Samsung Wallet application first checks if a driver's license is enrolled. If a license is found, the application generates "device engagement bytes" in accordance with the ISO-18013-5 specification. These bytes are then transmitted to the server's /key API endpoint to establish a verification session.
The POST request body is JSON with a single field called data. This field value is the JWT containing encrypted device engagement bytes.
{"data": "………"} In the /key API endpoint,
Accept the POST request sent to the path /{cardId}/{refId}/key Extract the data field from the body as the JWT and decrypt it to receive the device engagement bytes. Establish a session using the device engagement bytes. Create the mDoc request data and send it back to the Samsung Wallet application as response to the POST request. The complete process is shown below:
@PostMapping("{cardId}/{refId}/key") fun receiveKey( @PathVariable cardId: String, @PathVariable refId: String, @RequestBody body: String ): ResponseEntity<String> { val cData = JsonParser.parseString(body).asJsonObject.get("data").toString() val base64EngagementBytes = JwtGen.decryptBase64Engagement(cData) val mDoc18013 = createMDoc10813(base64EngagementBytes) val cdataResponse = "{\"data\": \"${JwtGen.generateRequestJwt(mDoc18013)}\"}" return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(cdataResponse) } Decrypt the device engagement bytes from the request body
The data field value received in the /key API contains the required device engagement bytes encoded in the JWT format. Simply decrypt the JWT in order to retrieve the device engagement bytes. Here the decryptBase64Engagement() function is defined as follows:
fun decryptBase64Engagement(data: String): ByteArray { val signedJWT: SignedJWT = SignedJWT.parse(data) val payload = signedJWT.payload val jwe = JWEObject.parse(payload.toString()) val partnerPrivateKey = KeyUtil.readPrivateKey(PARTNER_PRIVATE_KEY) val decrypter = RSADecrypter(partnerPrivateKey) jwe.decrypt(decrypter) val base64Engagement = jwe.payload.toJSONObject().get("data").toString() val base64EngagementBytes = Base64.getUrlDecoder().decode(base64Engagement) return base64EngagementBytes } Simply perform the JWT decryption operation using your private key to get the decrypted JWE payload in the JSON format. In the JSON-formatted payload, the data field contains the device engagement bytes encoded in the base64URL string format. Decode the string using a base64URL decoder and you get the final device engagement bytes.
Create a shared session using device engagement bytes
The createMDoc10813(base64EngagementBytes) function creates a shared session between the /key API and /auth API using a companion object. Having a shared session between the two endpoints is mandatory in order to decrypt the information provided by the Samsung Wallet application later on.
Inside the companion object, we also need to generate an elliptic curve keypair in order to establish the encrypted session. The companion object is defined as shown below:
companion object{ val keyPair = KeyUtil.generateEcKeyPair() var mDoc18013: Mdoc18013? = null fun createMDoc10813(base64EngagementBytes: ByteArray): Mdoc18013 { if (mDoc18013 == null ) { mDoc18013 = Mdoc18013(keyPair, base64EngagementBytes ) return mDoc18013!! } else{ return mDoc18013!! } } fun getMDoc10813(): Mdoc18013 { return mDoc18013!! } } The elliptic curve keypair is generated using a simple KeyPairGenerator class instance.
fun generateEcKeyPair(): KeyPair { val keyPairGenerator = KeyPairGenerator.getInstance("EC") val ecGenParameterSpec = ECGenParameterSpec("secp256r1") keyPairGenerator.initialize(ecGenParameterSpec) return keyPairGenerator.generateKeyPair() } Prepare the mDoc request data
Preparing the mDoc request data is the most crucial part of the VWW operation. The request data defines the data that needs to be retrieved from mDL. The generateRequestJwt() function can be divided into several parts:
Define and encode the request data payload. Encrypt the device request. Create session establishment data using the encrypted device request bytes. Create a signed JWT. Below, we go through these steps one at a time.
Define the request data payload and encode it to a CBOR Byte Array
// Define requested data fields val requestData = """ { "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": true, "age_in_years": true, "issue_date": true, "expiry_date": true, "document_number": false, "issuing_authority": false }, "org.iso.18013.5.1.aamva": { "DHS_compliance": false } } } """.trimIndent() // CBOR encoding process with tagging val firstEncoded = CBORObject.FromJSONString(requestData).EncodeToBytes() val thirdEncoded = CBORObject.FromObjectAndTag(firstEncoded, 24).EncodeToBytes() val itemRequestBytesList = listOf(thirdEncoded) // Create mDoc items requests array val docRequestsArray = CBORObject.NewArray() itemRequestBytesList.forEach { val docRequest = CBORObject.NewMap() docRequest.set("itemsRequest", CBORObject.DecodeFromBytes(it)) docRequestsArray.Add(docRequest) } // Create device request using docRequestArray val deviceRequest = CBORObject.NewMap() deviceRequest.set("version", CBORObject.FromObject("1.0")) deviceRequest.set("docRequests", docRequestsArray) Encrypt the device request
val encryptedDeviceRequestBytes = mDoc18013.encryptDeviceRequest(deviceRequest.EncodeToBytes()) Create session establishment data using the encrypted device request bytes
val establishment = CBORObject.NewMap() establishment.set("eReaderKey", CBORObject.FromObjectAndTag(mDoc18013.getEReaderKey(),24)) establishment.set("data", CBORObject.FromObject(encryptedDeviceRequestBytes)) val establishmentString = Base64.getUrlEncoder().encodeToString(establishment.EncodeToBytes()) Create a signed JWT using the establishmentString as the JWE payload
val jweObj = JWEObject(JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM).build(), Payload(establishmentString)) val encryptor = RSAEncrypter(samsungPublicKey as RSAPublicKey) jweObj.encrypt(encryptor) val jwsHeader = JWSHeader.Builder(JWSAlgorithm.RS256) .contentType("AUTH") .customParam("partnerId", "412255212345678910") .customParam("certificateId", "A123") .customParam("ver", "3") .customParam("utc", System.currentTimeMillis()) .build() val jwsObj = JWSObject(jwsHeader, Payload(jweObj.serialize())) val rsaJwk = RSAKey.Builder(partnerPublicKey as RSAPublicKey).privateKey(partnerPrivateKey).build() val jwsSigner = RSASSASigner(rsaJwk) jwsObj.sign(jwsSigner) return jwsObj.serialize() Now, we can send this JWT back as the response from the /key API.
If everything is done properly, the Samsung Wallet application should receive the verification request along with the list of requested fields. After processing and verifying the request, the Samsung Wallet application needs to prompt the user to verify their identity. Once the user verifies their identity using the application, it sends the requested information back to the /auth API endpoint.
Next, let's define the /auth API endpoint to retrieve the requested information.
Define the /auth API endpoint
Similar to the previously defined /key API endpoint, the /auth API endpoint also receives a single JSON payload with a single field called data, which contains the requested information in a JWT encoded format.
{"data": "………"} Decrypt the JWT payload from the request body
We can extract the data field and decrypt the JWT following the same process used in the /key API.
@PostMapping("{cardId}/{refId}/auth") fun receiveAuth( @PathVariable cardId: String, @PathVariable refId: String, @RequestBody body: String ): HttpStatus { val responseData = JsonParser.parseString(body).asJsonObject.get("data").toString() val signedJWT: SignedJWT = SignedJWT.parse(responseData) val payload = signedJWT.payload val jwe = JWEObject.parse(payload.toString()) val partnerPrivateKey = JwtGen.partnerPrivateKey val decrypter = RSADecrypter(partnerPrivateKey) jwe.decrypt(decrypter) // Process and decrypt the data until the requested information is retrieved return HttpStatus.OK } After the decryption operation, we get another JSON object in the decrypted JWE payload. In this JSON payload, the data field contains the encoded data of the information we requested. To decode and decrypt this data:
Decode the extracted data field value using Base64URL decoder. This gives us the encrypted mDoc response in a CBOR byte array. Decode the CBOR object from the byte array and get the mDoc data from the data field. Decrypt the mDoc data using the mDoc18013.decryptMdocResponse() function to retrieve the plain response in the JSON format. WarningThe mDoc18013 instance used for this step must be the same instance used in the /key API. Otherwise, the decryption operation fails. val mDoc18013 = getMDoc10813() val cborData = jwe.payload.toJSONObject().get("data").toString() val decodedData = Base64.getUrlDecoder().decode(cborData) val mDocResponse = CBORObject.DecodeFromBytes(decodedData) val mDocData = mDocResponse.get("data") val decryptMdocResponseBytes = mDoc18013.decryptMdocResponse(mDocData.GetByteString()) val plainResponse = CBORObject.DecodeFromBytes(decryptMdocResponseBytes).ToJSONString() After these steps, we finally have the mDoc response in a plain JSON format.
{ "status": 0, "version": "1.0", "documents": [ { "docType": "org.iso.18013.5.1.mDL", "deviceSigned": {}, "issuerSigned": { "issuerAuth": ["......."], "nameSpaces": { "org.iso.18013.5.1": [ "pGhkaWdlc3RJRBkhfWZyYW5kb21Uczc4ZnY4c2NoNGMyZHR5MnlyOTZxZWxlbWVudElkZW50aWZpZXJsYWdlX2luX3llYXJzbGVsZW1lbnRWYWx1ZRgs", "pGhkaWdlc3………" ], "org.iso.18013.5.1.aamva": [ "pGhkaWdlc3RJRBlLD2ZyYW5kb21Uczh5cmptbTU4OHMyNzY4emozNm5xZWxlbWVudElkZW50aWZpZXJuREhTX2NvbXBsaWFuY2VsZWxlbWVudFZhbHVlYUY" ] } } } ] } Here, the values inside the org.iso.18013.5.1 and org.iso.18013.5.1.aamva are the fields we initially requested in the Key API. Simply decode these CBOR-encoded fields to retrieve the information you requested. For example, the "pGhkaWdlc3RJRBkhfWZyYW5kb21Uczc4ZnY4c2NoNGMyZHR5MnlyOTZxZWxlbWVudElkZW50aWZpZXJsYWdlX2luX3llYXJzbGVsZW1lbnRWYWx1ZRgs" value informs us that element name is age_in_years and its value is "44," meaning the subject is 44 years old. We can extract the rest of the requested information by decoding the other provided values in the same way.
Figure 1: Verifying user identity using VWW Web2App process
Summary
In this tutorial, we learned how we can implement user identity verification on a website utilizing Samsung Wallet's Verify with Wallet functionality. By making use of the Web2App method discussed in this article, you can allow users to securely confirm and verify their digital identity using their mobile driver's licenses.
Related resources
ISO/IEC 18013-5:2021 - Personal identification — ISO-compliant driving licence — Part 5: Mobile driving licence (mDL) application Mobile Driver License - American Association of Motor Vehicle Administrators - AAMVA Verify with Wallet API Guidelines Relying Party Card Specifications Sample Code Download Link View the full blog at its source
-
By Samsung Newsroom
In a previous blog article, we learned how we could utilize Samsung Wallet's RP SDK in order to verify a user's identity from an Android application. In this tutorial, we learn how to verify a user's identity directly from a website using Samsung Wallet and its Web2App API in a Spring Boot web server.
Prerequisites
The process described has the following prerequisites:
A valid US driver's license or state ID for the user whose identity is to be verified The Samsung Galaxy device used needs to be registered for the US region and have mDL support Complete the Samsung Wallet Partner onboarding process Create a Wallet Card template with the Relying Party type in the Samsung Wallet Partners Portal Implementing the Verify with Wallet functionality in your website
The Verify with Wallet (VWW) process utilizing the Web2App method consists of two distinct parts.
The "Verify with Samsung Wallet" button. This button contains the VWW link with the RP card data tokenized as the CData. The user can click this VWW link to initiate the verification process. The partner server containing the /key and /auth endpoints. The partner server processes the requests sent from the Samsung Wallet application and handles the complete VWW process. Frontend configuration
In order to initiate the Verify with Wallet process, we need to implement a "Verify with Samsung Wallet" button in a webpage.
Implementing the button is a very simple process similar to creating a traditional "Add to Wallet" button. We can make use of the data transmit link approach and create a button that contains the VWW link: https://a.swallet.link/vww/v1/{cardId}#Clip?cdata={cdata}
Replace {cardId} with the ID of your own card. Meanwhile, the CData value needs to be generated in real time. This is done using a process similar to generating CData for ATW operation, only with the payload contained being different according to the specification for the Relying Party card type. Check out the sample code for the complete process of CData generation and using it in the button.
Backend configuration
Once the button implementation is complete, you need to configure your server to handle the exchange of information between your server and Samsung Wallet application. The VWW process requires the partner to define 2 API endpoints:
/rp/v1.0/{cardId}/{refId}/key: Establishes a secure session and prepares the request data for the process. /rp/v1.0/{cardId}/{refId}/auth: Processes encrypted authentication data and mDL data received from the Wallet application. The workflow for the information exchange is as follows:
Once the VWW button is clicked, the Samsung Wallet application opens. Samsung Wallet checks if the device has a driver's license already enrolled in the device. If an mDL already exists on the device, the Samsung Wallet application calls the /key endpoint to establish a session. After establishing session with the partner server and retrieving the mDoc request, the Samsung Wallet application prompts the user to confirm if they wish to share their information with the partner. After the user confirms that they wish to proceed, the application finally sends the requested information to the /auth API to complete the VWW process. Define the /key endpoint
When the user clicks the "Verify with Samsung Wallet" button, the Samsung Wallet application first checks if a driver's license is enrolled. If a license is found, the application generates "device engagement bytes" in accordance with the ISO-18013-5 specification. These bytes are then transmitted to the server's /key API endpoint to establish a verification session.
The POST request body is JSON with a single field called data. This field value is the JWT containing encrypted device engagement bytes.
{"data": "………"} In the /key API endpoint,
Accept the POST request sent to the path /{cardId}/{refId}/key Extract the data field from the body as the JWT and decrypt it to receive the device engagement bytes. Establish a session using the device engagement bytes. Create the mDoc request data and send it back to the Samsung Wallet application as response to the POST request. The complete process is shown below:
@PostMapping("{cardId}/{refId}/key") fun receiveKey( @PathVariable cardId: String, @PathVariable refId: String, @RequestBody body: String ): ResponseEntity<String> { val cData = JsonParser.parseString(body).asJsonObject.get("data").toString() val base64EngagementBytes = JwtGen.decryptBase64Engagement(cData) val mDoc18013 = createMDoc10813(base64EngagementBytes) val cdataResponse = "{\"data\": \"${JwtGen.generateRequestJwt(mDoc18013)}\"}" return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(cdataResponse) } Decrypt the device engagement bytes from the request body
The data field value received in the /key API contains the required device engagement bytes encoded in the JWT format. Simply decrypt the JWT in order to retrieve the device engagement bytes. Here the decryptBase64Engagement() function is defined as follows:
fun decryptBase64Engagement(data: String): ByteArray { val signedJWT: SignedJWT = SignedJWT.parse(data) val payload = signedJWT.payload val jwe = JWEObject.parse(payload.toString()) val partnerPrivateKey = KeyUtil.readPrivateKey(PARTNER_PRIVATE_KEY) val decrypter = RSADecrypter(partnerPrivateKey) jwe.decrypt(decrypter) val base64Engagement = jwe.payload.toJSONObject().get("data").toString() val base64EngagementBytes = Base64.getUrlDecoder().decode(base64Engagement) return base64EngagementBytes } Simply perform the JWT decryption operation using your private key to get the decrypted JWE payload in the JSON format. In the JSON-formatted payload, the data field contains the device engagement bytes encoded in the base64URL string format. Decode the string using a base64URL decoder and you get the final device engagement bytes.
Create a shared session using device engagement bytes
The createMDoc10813(base64EngagementBytes) function creates a shared session between the /key API and /auth API using a companion object. Having a shared session between the two endpoints is mandatory in order to decrypt the information provided by the Samsung Wallet application later on.
Inside the companion object, we also need to generate an elliptic curve keypair in order to establish the encrypted session. The companion object is defined as shown below:
companion object{ val keyPair = KeyUtil.generateEcKeyPair() var mDoc18013: Mdoc18013? = null fun createMDoc10813(base64EngagementBytes: ByteArray): Mdoc18013 { if (mDoc18013 == null ) { mDoc18013 = Mdoc18013(keyPair, base64EngagementBytes ) return mDoc18013!! } else{ return mDoc18013!! } } fun getMDoc10813(): Mdoc18013 { return mDoc18013!! } } The elliptic curve keypair is generated using a simple KeyPairGenerator class instance.
fun generateEcKeyPair(): KeyPair { val keyPairGenerator = KeyPairGenerator.getInstance("EC") val ecGenParameterSpec = ECGenParameterSpec("secp256r1") keyPairGenerator.initialize(ecGenParameterSpec) return keyPairGenerator.generateKeyPair() } Prepare the mDoc request data
Preparing the mDoc request data is the most crucial part of the VWW operation. The request data defines the data that needs to be retrieved from mDL. The generateRequestJwt() function can be divided into several parts:
Define and encode the request data payload. Encrypt the device request. Create session establishment data using the encrypted device request bytes. Create a signed JWT. Below, we go through these steps one at a time.
Define the request data payload and encode it to a CBOR Byte Array
// Define requested data fields val requestData = """ { "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": true, "age_in_years": true, "issue_date": true, "expiry_date": true, "document_number": false, "issuing_authority": false }, "org.iso.18013.5.1.aamva": { "DHS_compliance": false } } } """.trimIndent() // CBOR encoding process with tagging val firstEncoded = CBORObject.FromJSONString(requestData).EncodeToBytes() val thirdEncoded = CBORObject.FromObjectAndTag(firstEncoded, 24).EncodeToBytes() val itemRequestBytesList = listOf(thirdEncoded) // Create mDoc items requests array val docRequestsArray = CBORObject.NewArray() itemRequestBytesList.forEach { val docRequest = CBORObject.NewMap() docRequest.set("itemsRequest", CBORObject.DecodeFromBytes(it)) docRequestsArray.Add(docRequest) } // Create device request using docRequestArray val deviceRequest = CBORObject.NewMap() deviceRequest.set("version", CBORObject.FromObject("1.0")) deviceRequest.set("docRequests", docRequestsArray) Encrypt the device request
val encryptedDeviceRequestBytes = mDoc18013.encryptDeviceRequest(deviceRequest.EncodeToBytes()) Create session establishment data using the encrypted device request bytes
val establishment = CBORObject.NewMap() establishment.set("eReaderKey", CBORObject.FromObjectAndTag(mDoc18013.getEReaderKey(),24)) establishment.set("data", CBORObject.FromObject(encryptedDeviceRequestBytes)) val establishmentString = Base64.getUrlEncoder().encodeToString(establishment.EncodeToBytes()) Create a signed JWT using the establishmentString as the JWE payload
val jweObj = JWEObject(JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM).build(), Payload(establishmentString)) val encryptor = RSAEncrypter(samsungPublicKey as RSAPublicKey) jweObj.encrypt(encryptor) val jwsHeader = JWSHeader.Builder(JWSAlgorithm.RS256) .contentType("AUTH") .customParam("partnerId", "412255212345678910") .customParam("certificateId", "A123") .customParam("ver", "3") .customParam("utc", System.currentTimeMillis()) .build() val jwsObj = JWSObject(jwsHeader, Payload(jweObj.serialize())) val rsaJwk = RSAKey.Builder(partnerPublicKey as RSAPublicKey).privateKey(partnerPrivateKey).build() val jwsSigner = RSASSASigner(rsaJwk) jwsObj.sign(jwsSigner) return jwsObj.serialize() Now, we can send this JWT back as the response from the /key API.
If everything is done properly, the Samsung Wallet application should receive the verification request along with the list of requested fields. After processing and verifying the request, the Samsung Wallet application needs to prompt the user to verify their identity. Once the user verifies their identity using the application, it sends the requested information back to the /auth API endpoint.
Next, let's define the /auth API endpoint to retrieve the requested information.
Define the /auth API endpoint
Similar to the previously defined /key API endpoint, the /auth API endpoint also receives a single JSON payload with a single field called data, which contains the requested information in a JWT encoded format.
{"data": "………"} Decrypt the JWT payload from the request body
We can extract the data field and decrypt the JWT following the same process used in the /key API.
@PostMapping("{cardId}/{refId}/auth") fun receiveAuth( @PathVariable cardId: String, @PathVariable refId: String, @RequestBody body: String ): HttpStatus { val responseData = JsonParser.parseString(body).asJsonObject.get("data").toString() val signedJWT: SignedJWT = SignedJWT.parse(responseData) val payload = signedJWT.payload val jwe = JWEObject.parse(payload.toString()) val partnerPrivateKey = JwtGen.partnerPrivateKey val decrypter = RSADecrypter(partnerPrivateKey) jwe.decrypt(decrypter) // Process and decrypt the data until the requested information is retrieved return HttpStatus.OK } After the decryption operation, we get another JSON object in the decrypted JWE payload. In this JSON payload, the data field contains the encoded data of the information we requested. To decode and decrypt this data:
Decode the extracted data field value using Base64URL decoder. This gives us the encrypted mDoc response in a CBOR byte array. Decode the CBOR object from the byte array and get the mDoc data from the data field. Decrypt the mDoc data using the mDoc18013.decryptMdocResponse() function to retrieve the plain response in the JSON format. WarningThe mDoc18013 instance used for this step must be the same instance used in the /key API. Otherwise, the decryption operation fails. val mDoc18013 = getMDoc10813() val cborData = jwe.payload.toJSONObject().get("data").toString() val decodedData = Base64.getUrlDecoder().decode(cborData) val mDocResponse = CBORObject.DecodeFromBytes(decodedData) val mDocData = mDocResponse.get("data") val decryptMdocResponseBytes = mDoc18013.decryptMdocResponse(mDocData.GetByteString()) val plainResponse = CBORObject.DecodeFromBytes(decryptMdocResponseBytes).ToJSONString() After these steps, we finally have the mDoc response in a plain JSON format.
{ "status": 0, "version": "1.0", "documents": [ { "docType": "org.iso.18013.5.1.mDL", "deviceSigned": {}, "issuerSigned": { "issuerAuth": ["......."], "nameSpaces": { "org.iso.18013.5.1": [ "pGhkaWdlc3RJRBkhfWZyYW5kb21Uczc4ZnY4c2NoNGMyZHR5MnlyOTZxZWxlbWVudElkZW50aWZpZXJsYWdlX2luX3llYXJzbGVsZW1lbnRWYWx1ZRgs", "pGhkaWdlc3………" ], "org.iso.18013.5.1.aamva": [ "pGhkaWdlc3RJRBlLD2ZyYW5kb21Uczh5cmptbTU4OHMyNzY4emozNm5xZWxlbWVudElkZW50aWZpZXJuREhTX2NvbXBsaWFuY2VsZWxlbWVudFZhbHVlYUY" ] } } } ] } Here, the values inside the org.iso.18013.5.1 and org.iso.18013.5.1.aamva are the fields we initially requested in the Key API. Simply decode these CBOR-encoded fields to retrieve the information you requested. For example, the "pGhkaWdlc3RJRBkhfWZyYW5kb21Uczc4ZnY4c2NoNGMyZHR5MnlyOTZxZWxlbWVudElkZW50aWZpZXJsYWdlX2luX3llYXJzbGVsZW1lbnRWYWx1ZRgs" value informs us that element name is age_in_years and its value is "44," meaning the subject is 44 years old. We can extract the rest of the requested information by decoding the other provided values in the same way.
Figure 1: Verifying user identity using VWW Web2App process
Summary
In this tutorial, we learned how we can implement user identity verification on a website utilizing Samsung Wallet's Verify with Wallet functionality. By making use of the Web2App method discussed in this article, you can allow users to securely confirm and verify their digital identity using their mobile driver's licenses.
Related resources
ISO/IEC 18013-5:2021 - Personal identification — ISO-compliant driving licence — Part 5: Mobile driving licence (mDL) application Mobile Driver License - American Association of Motor Vehicle Administrators - AAMVA Verify with Wallet API Guidelines Relying Party Card Specifications Sample Code Download Link View the full blog at its source
-
By Samsung Newsroom
In a previous blog article, we learned how we could utilize Samsung Wallet's RP SDK in order to verify a user's identity from an Android application. In this tutorial, we learn how to verify a user's identity directly from a website using Samsung Wallet and its Web2App API in a Spring Boot web server.
Prerequisites
The process described has the following prerequisites:
A valid US driver's license or state ID for the user whose identity is to be verified The Samsung Galaxy device used needs to be registered for the US region and have mDL support Complete the Samsung Wallet Partner onboarding process Create a Wallet Card template with the Relying Party type in the Samsung Wallet Partners Portal Implementing the Verify with Wallet functionality in your website
The Verify with Wallet (VWW) process utilizing the Web2App method consists of two distinct parts.
The "Verify with Samsung Wallet" button. This button contains the VWW link with the RP card data tokenized as the CData. The user can click this VWW link to initiate the verification process. The partner server containing the /key and /auth endpoints. The partner server processes the requests sent from the Samsung Wallet application and handles the complete VWW process. Frontend configuration
In order to initiate the Verify with Wallet process, we need to implement a "Verify with Samsung Wallet" button in a webpage.
Implementing the button is a very simple process similar to creating a traditional "Add to Wallet" button. We can make use of the data transmit link approach and create a button that contains the VWW link: https://a.swallet.link/vww/v1/{cardId}#Clip?cdata={cdata}
Replace {cardId} with the ID of your own card. Meanwhile, the CData value needs to be generated in real time. This is done using a process similar to generating CData for ATW operation, only with the payload contained being different according to the specification for the Relying Party card type. Check out the sample code for the complete process of CData generation and using it in the button.
Backend configuration
Once the button implementation is complete, you need to configure your server to handle the exchange of information between your server and Samsung Wallet application. The VWW process requires the partner to define 2 API endpoints:
/rp/v1.0/{cardId}/{refId}/key: Establishes a secure session and prepares the request data for the process. /rp/v1.0/{cardId}/{refId}/auth: Processes encrypted authentication data and mDL data received from the Wallet application. The workflow for the information exchange is as follows:
Once the VWW button is clicked, the Samsung Wallet application opens. Samsung Wallet checks if the device has a driver's license already enrolled in the device. If an mDL already exists on the device, the Samsung Wallet application calls the /key endpoint to establish a session. After establishing session with the partner server and retrieving the mDoc request, the Samsung Wallet application prompts the user to confirm if they wish to share their information with the partner. After the user confirms that they wish to proceed, the application finally sends the requested information to the /auth API to complete the VWW process. Define the /key endpoint
When the user clicks the "Verify with Samsung Wallet" button, the Samsung Wallet application first checks if a driver's license is enrolled. If a license is found, the application generates "device engagement bytes" in accordance with the ISO-18013-5 specification. These bytes are then transmitted to the server's /key API endpoint to establish a verification session.
The POST request body is JSON with a single field called data. This field value is the JWT containing encrypted device engagement bytes.
{"data": "………"} In the /key API endpoint,
Accept the POST request sent to the path /{cardId}/{refId}/key Extract the data field from the body as the JWT and decrypt it to receive the device engagement bytes. Establish a session using the device engagement bytes. Create the mDoc request data and send it back to the Samsung Wallet application as response to the POST request. The complete process is shown below:
@PostMapping("{cardId}/{refId}/key") fun receiveKey( @PathVariable cardId: String, @PathVariable refId: String, @RequestBody body: String ): ResponseEntity<String> { val cData = JsonParser.parseString(body).asJsonObject.get("data").toString() val base64EngagementBytes = JwtGen.decryptBase64Engagement(cData) val mDoc18013 = createMDoc10813(base64EngagementBytes) val cdataResponse = "{\"data\": \"${JwtGen.generateRequestJwt(mDoc18013)}\"}" return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(cdataResponse) } Decrypt the device engagement bytes from the request body
The data field value received in the /key API contains the required device engagement bytes encoded in the JWT format. Simply decrypt the JWT in order to retrieve the device engagement bytes. Here the decryptBase64Engagement() function is defined as follows:
fun decryptBase64Engagement(data: String): ByteArray { val signedJWT: SignedJWT = SignedJWT.parse(data) val payload = signedJWT.payload val jwe = JWEObject.parse(payload.toString()) val partnerPrivateKey = KeyUtil.readPrivateKey(PARTNER_PRIVATE_KEY) val decrypter = RSADecrypter(partnerPrivateKey) jwe.decrypt(decrypter) val base64Engagement = jwe.payload.toJSONObject().get("data").toString() val base64EngagementBytes = Base64.getUrlDecoder().decode(base64Engagement) return base64EngagementBytes } Simply perform the JWT decryption operation using your private key to get the decrypted JWE payload in the JSON format. In the JSON-formatted payload, the data field contains the device engagement bytes encoded in the base64URL string format. Decode the string using a base64URL decoder and you get the final device engagement bytes.
Create a shared session using device engagement bytes
The createMDoc10813(base64EngagementBytes) function creates a shared session between the /key API and /auth API using a companion object. Having a shared session between the two endpoints is mandatory in order to decrypt the information provided by the Samsung Wallet application later on.
Inside the companion object, we also need to generate an elliptic curve keypair in order to establish the encrypted session. The companion object is defined as shown below:
companion object{ val keyPair = KeyUtil.generateEcKeyPair() var mDoc18013: Mdoc18013? = null fun createMDoc10813(base64EngagementBytes: ByteArray): Mdoc18013 { if (mDoc18013 == null ) { mDoc18013 = Mdoc18013(keyPair, base64EngagementBytes ) return mDoc18013!! } else{ return mDoc18013!! } } fun getMDoc10813(): Mdoc18013 { return mDoc18013!! } } The elliptic curve keypair is generated using a simple KeyPairGenerator class instance.
fun generateEcKeyPair(): KeyPair { val keyPairGenerator = KeyPairGenerator.getInstance("EC") val ecGenParameterSpec = ECGenParameterSpec("secp256r1") keyPairGenerator.initialize(ecGenParameterSpec) return keyPairGenerator.generateKeyPair() } Prepare the mDoc request data
Preparing the mDoc request data is the most crucial part of the VWW operation. The request data defines the data that needs to be retrieved from mDL. The generateRequestJwt() function can be divided into several parts:
Define and encode the request data payload. Encrypt the device request. Create session establishment data using the encrypted device request bytes. Create a signed JWT. Below, we go through these steps one at a time.
Define the request data payload and encode it to a CBOR Byte Array
// Define requested data fields val requestData = """ { "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": true, "age_in_years": true, "issue_date": true, "expiry_date": true, "document_number": false, "issuing_authority": false }, "org.iso.18013.5.1.aamva": { "DHS_compliance": false } } } """.trimIndent() // CBOR encoding process with tagging val firstEncoded = CBORObject.FromJSONString(requestData).EncodeToBytes() val thirdEncoded = CBORObject.FromObjectAndTag(firstEncoded, 24).EncodeToBytes() val itemRequestBytesList = listOf(thirdEncoded) // Create mDoc items requests array val docRequestsArray = CBORObject.NewArray() itemRequestBytesList.forEach { val docRequest = CBORObject.NewMap() docRequest.set("itemsRequest", CBORObject.DecodeFromBytes(it)) docRequestsArray.Add(docRequest) } // Create device request using docRequestArray val deviceRequest = CBORObject.NewMap() deviceRequest.set("version", CBORObject.FromObject("1.0")) deviceRequest.set("docRequests", docRequestsArray) Encrypt the device request
val encryptedDeviceRequestBytes = mDoc18013.encryptDeviceRequest(deviceRequest.EncodeToBytes()) Create session establishment data using the encrypted device request bytes
val establishment = CBORObject.NewMap() establishment.set("eReaderKey", CBORObject.FromObjectAndTag(mDoc18013.getEReaderKey(),24)) establishment.set("data", CBORObject.FromObject(encryptedDeviceRequestBytes)) val establishmentString = Base64.getUrlEncoder().encodeToString(establishment.EncodeToBytes()) Create a signed JWT using the establishmentString as the JWE payload
val jweObj = JWEObject(JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM).build(), Payload(establishmentString)) val encryptor = RSAEncrypter(samsungPublicKey as RSAPublicKey) jweObj.encrypt(encryptor) val jwsHeader = JWSHeader.Builder(JWSAlgorithm.RS256) .contentType("AUTH") .customParam("partnerId", "412255212345678910") .customParam("certificateId", "A123") .customParam("ver", "3") .customParam("utc", System.currentTimeMillis()) .build() val jwsObj = JWSObject(jwsHeader, Payload(jweObj.serialize())) val rsaJwk = RSAKey.Builder(partnerPublicKey as RSAPublicKey).privateKey(partnerPrivateKey).build() val jwsSigner = RSASSASigner(rsaJwk) jwsObj.sign(jwsSigner) return jwsObj.serialize() Now, we can send this JWT back as the response from the /key API.
If everything is done properly, the Samsung Wallet application should receive the verification request along with the list of requested fields. After processing and verifying the request, the Samsung Wallet application needs to prompt the user to verify their identity. Once the user verifies their identity using the application, it sends the requested information back to the /auth API endpoint.
Next, let's define the /auth API endpoint to retrieve the requested information.
Define the /auth API endpoint
Similar to the previously defined /key API endpoint, the /auth API endpoint also receives a single JSON payload with a single field called data, which contains the requested information in a JWT encoded format.
{"data": "………"} Decrypt the JWT payload from the request body
We can extract the data field and decrypt the JWT following the same process used in the /key API.
@PostMapping("{cardId}/{refId}/auth") fun receiveAuth( @PathVariable cardId: String, @PathVariable refId: String, @RequestBody body: String ): HttpStatus { val responseData = JsonParser.parseString(body).asJsonObject.get("data").toString() val signedJWT: SignedJWT = SignedJWT.parse(responseData) val payload = signedJWT.payload val jwe = JWEObject.parse(payload.toString()) val partnerPrivateKey = JwtGen.partnerPrivateKey val decrypter = RSADecrypter(partnerPrivateKey) jwe.decrypt(decrypter) // Process and decrypt the data until the requested information is retrieved return HttpStatus.OK } After the decryption operation, we get another JSON object in the decrypted JWE payload. In this JSON payload, the data field contains the encoded data of the information we requested. To decode and decrypt this data:
Decode the extracted data field value using Base64URL decoder. This gives us the encrypted mDoc response in a CBOR byte array. Decode the CBOR object from the byte array and get the mDoc data from the data field. Decrypt the mDoc data using the mDoc18013.decryptMdocResponse() function to retrieve the plain response in the JSON format. WarningThe mDoc18013 instance used for this step must be the same instance used in the /key API. Otherwise, the decryption operation fails. val mDoc18013 = getMDoc10813() val cborData = jwe.payload.toJSONObject().get("data").toString() val decodedData = Base64.getUrlDecoder().decode(cborData) val mDocResponse = CBORObject.DecodeFromBytes(decodedData) val mDocData = mDocResponse.get("data") val decryptMdocResponseBytes = mDoc18013.decryptMdocResponse(mDocData.GetByteString()) val plainResponse = CBORObject.DecodeFromBytes(decryptMdocResponseBytes).ToJSONString() After these steps, we finally have the mDoc response in a plain JSON format.
{ "status": 0, "version": "1.0", "documents": [ { "docType": "org.iso.18013.5.1.mDL", "deviceSigned": {}, "issuerSigned": { "issuerAuth": ["......."], "nameSpaces": { "org.iso.18013.5.1": [ "pGhkaWdlc3RJRBkhfWZyYW5kb21Uczc4ZnY4c2NoNGMyZHR5MnlyOTZxZWxlbWVudElkZW50aWZpZXJsYWdlX2luX3llYXJzbGVsZW1lbnRWYWx1ZRgs", "pGhkaWdlc3………" ], "org.iso.18013.5.1.aamva": [ "pGhkaWdlc3RJRBlLD2ZyYW5kb21Uczh5cmptbTU4OHMyNzY4emozNm5xZWxlbWVudElkZW50aWZpZXJuREhTX2NvbXBsaWFuY2VsZWxlbWVudFZhbHVlYUY" ] } } } ] } Here, the values inside the org.iso.18013.5.1 and org.iso.18013.5.1.aamva are the fields we initially requested in the Key API. Simply decode these CBOR-encoded fields to retrieve the information you requested. For example, the "pGhkaWdlc3RJRBkhfWZyYW5kb21Uczc4ZnY4c2NoNGMyZHR5MnlyOTZxZWxlbWVudElkZW50aWZpZXJsYWdlX2luX3llYXJzbGVsZW1lbnRWYWx1ZRgs" value informs us that element name is age_in_years and its value is "44," meaning the subject is 44 years old. We can extract the rest of the requested information by decoding the other provided values in the same way.
Figure 1: Verifying user identity using VWW Web2App process
Summary
In this tutorial, we learned how we can implement user identity verification on a website utilizing Samsung Wallet's Verify with Wallet functionality. By making use of the Web2App method discussed in this article, you can allow users to securely confirm and verify their digital identity using their mobile driver's licenses.
Related resources
ISO/IEC 18013-5:2021 - Personal identification — ISO-compliant driving licence — Part 5: Mobile driving licence (mDL) application Mobile Driver License - American Association of Motor Vehicle Administrators - AAMVA Verify with Wallet API Guidelines Relying Party Card Specifications Sample Code Download Link View the full blog at its source
-
-
By Samsung Newsroom
Digital identity verification has become a rising topic in the current technological landscape. Samsung Wallet allows Samsung Galaxy device users to securely register their state-issued US driver's license in their device, letting them use it as a mobile driver's license (mDL). Through the "Verify with Wallet" (VWW) functionality, Samsung Wallet provides Android developers with the ability to authenticate a user's identity directly from their application by utilizing the user's registered mDL on the device. The implementation of the functionality is based on and is fully compliant with the ISO 18013-5 standard. In this article, we explore the complete process of implementing Verify with Wallet in an Android application.
Prerequisites
In order to complete the tasks in this article and implement a complete sample application for verifying a user's identity, you need the following:
Valid US driver's license or state ID US region Samsung Galaxy device with mDL support Complete the Samsung Wallet Partner onboarding process Understanding the Verify with Wallet process
Samsung Wallet offers a native Relying Party (RP) SDK for Android applications. RP SDK is an App2App SDK designed for enabling Samsung Wallet's mDL service in online use cases. By integrating this SDK, you can leverage the VWW functionality within their applications.
In your application, you need to create a JSON object for defining the request and a JSON payload for the Relying Party card. Then, you can utilize the RP SDK to create a valid mDoc request using the provided information. Finally, the request needs to be sent to the Samsung Wallet application.
In response, Samsung Wallet sends an encrypted response back to the application, which contains the requested information in a CBOR encoded format. The application can then decode the provided data and use it as necessary. Refer to the ISO 18013-5 standard, AAMVA mDL guidelines and the Samsung Wallet documentation for a better understanding of the VWW process.
Implementing the Verify with Wallet Functionality in Your Android Application
The process of implementing VWW in an Android application includes creating a Relying Party card for Samsung Wallet, downloading and integrating the RP SDK into the Android application and implementing the necessary functions in the Android application for completing the verification process.
Creating a Relying Party Wallet Card Template in the Samsung Wallet Partners Portal
In order to implement and use the VWW functionality, you need a wallet card of the Relying Party type for this purpose.
To create a Relying Party wallet card template:
Go to the Samsung Wallet Partners Portal. Select Wallet Card > Create Wallet Cards. From Wallet Card Templates, select Relying Party. Select the applicable Service Location and Authentication Issuer from the Advanced setting section. Make sure to select the proper values for the card, otherwise the verification process may not work. Figure 1: Creating a Relying Party card for VWW
Integrating the RP SDK in an Android Application
Once the Relying Party card template has been created, we can download and integrate the RP SDK to work with the Android application.
Step 1: Download the RP SDK for Android
To download the RP SDK:
Download the ZIP file containing the latest RP SDK release AAR file from Samsung Wallet Code Resources on the Samsung Developer website. Extract the AAR file from the downloaded ZIP file. Copy and paste the downloaded rp-sdk-x.xx-release.aar file inside a new directory in the Android Studio project (for example, \libs\). Step 2: Add Android Manifest Permissions
To implement the Verify with Wallet functionality, the application needs both the Internet access permission and the ability to query the installed Samsung Wallet application. To provide the application with these permissions, open the AndroidManifest.xml file in the Android Studio project and add the following lines:
<uses-permission android:name="android.permission.INTERNET" /> <queries> <package android:name="com.samsung.android.spay" /> </queries> Step 3: Add Gradle Dependencies
In the application's build.gradle file, load the RP SDK AAR file and the necessary additional dependencies for using the SDK, as follows:
// Load RP SDK AAR file implementation(files("libs/rp-sdk-1.05-release.aar")) //CBOR decoding dependencies implementation("com.upokecenter:cbor:4.0.1") implementation("com.augustcellars.cose:cose-java:1.1.0") // Other dependencies implementation("com.google.code.gson:gson:2.11.0") implementation("org.bouncycastle:bcprov-jdk15to18:1.66") implementation("com.nimbusds:nimbus-jose-jwt:9.37.3") implementation("io.reactivex.rxjava2:rxjava:2.2.21") implementation("io.reactivex.rxjava2:rxkotlin:2.4.0") implementation("io.reactivex.rxjava2:rxandroid:2.1.1") implementation("com.squareup.okhttp3:okhttp:4.11.0") After these steps, the RP SDK is ready for use in your Android application.
Configuring the Android Application for Verify with Wallet
Next, we need to complete the implementation of the Verify with Wallet functionality in your Android application.
Step 1: Build a Card Payload for the Relying Party Card
First, we need to create a request payload for the Relying Party card following the specification.
private fun buildApp2AppPayload(): String { return PAYLOAD .replace("{refId}", UUID.randomUUID().toString()) .replace("{createdAt}", System.currentTimeMillis().toString()) .replace("{updatedAt}", System.currentTimeMillis().toString()) } private val PAYLOAD = """ { "card": { "type": "relyingparty", "data": [ { "createdAt": {createdAt}, "updatedAt": {updatedAt}, "language": "en", "refId": "{refId}", "attributes": { "clientPackageName": "com.ahsan.verifyappsample", "clientType": "app", "fontColor": "#ffffff", "logoImage": "https://kr-cdn-gpp.mcsvc.samsung.com/mcp25/resource/2024/9/4/b940b7a2-0f55-42ce-8da7-025d50dbb6b7.png", "logoImage.darkUrl": "https://kr-cdn-gpp.mcsvc.samsung.com/mcp25/resource/2024/9/4/b940b7a2-0f55-42ce-8da7-025d50dbb6b7.png", "logoImage.lightUrl": "https://kr-cdn-gpp.mcsvc.samsung.com/mcp25/resource/2024/9/4/b940b7a2-0f55-42ce-8da7-025d50dbb6b7.png", "providerName": "Samsung Verification Sample" } } ] } } """.trimIndent() Step 2: Build the AppLink
The AppLink is a tokenized URL that is similar to the CData tokens used for Samsung Wallet cards. The Samsung Wallet RP SDK includes a function to generate the AppLink using the payload and the partner credentials (private key, public key, partner ID, card ID, certificate ID, etc.).
To build the AppLink, you can simply call the rpClientApis.buildAppLink() function with the required parameters:
val rpClientApis = RpClientApis(this) val appLink = rpClientApis.buildAppLink( partnerId = PARTNER_ID, cardId = CARD_ID, payload = buildApp2AppPayload(), samsungPublicKey = SAMSUNG_CERTIFICATE, partnerPublicKey = PARTNER_CERTIFICATE, partnerPrivateKey = PARTNER_PRIVATE_KEY, partnerCertificateId = CERTIFICATE_ID, isStagingServer = true ) Step 3: Build the Request Data
Finally, once the AppLink creation is complete, we can send the verification request using the RP SDK.
Before sending the request, we need to specify exactly which information we wish to retrieve. For this purpose, we need to create a JSON document following the ISO 18013-5 specification and specify the fields we wish to retrieve in the response. It is possible to request for the following fields in the request data under the "org.iso.18013.5.1" namespace:
portrait family_name given_name document_number age_in_years resident_address birth_date issue_date expiry_date sex height weight_range weight eye_colour hair_colour organ_donor driving_privileges veteran Additionally, it is also possible to request for the following 3 fields, under the "org.iso.18013.5.1.aamva" namespace:
domestic_driving_privileges DHS_compliance EDL_credential In our example, we only try to retrieve the following 4 fields: family_name, age_in_years, issue_date, and expiry_date. In the following code example, we build the request string accordingly:
val requestData = """ { "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": true, "age_in_years": true, "issue_date": true, "expiry_date": true } } } """.trimIndent() Step 4: Create the OnResponseListener Class
When using the VWW RP SDK, it is necessary to create a listener class for both sending the request and for receiving and processing the response from the mDoc server.
For our example, let's create an empty placeholder OnResponseListener class which extends the RP SDK's OnResponseListener class.
class OnResponseListener(private val requestData: String) : RpClientApis.OnResponseListener{ override fun onGetMdocRequestData(deviceEngagementBytes: ByteArray): ByteArray? { TODO("Not yet implemented") } override fun onMdocResponse(encryptedResponseBytes: ByteArray) { TODO("Not yet implemented") } override fun onMdocResponseFailed(exception: Exception) { Log.e(TAG, "Response processing failed", exception) } } Initiating the Verification Request
To initiate the identity verification process, we need to establish a secure session and send a structured request to the Samsung Wallet application. We can use the previously created OnResponseListener class for this purpose.
Step 1: Define the onGetMdocRequestData() Function for Sending the Request Data
Inside the onGetMdocRequestData() function, we need to do 2 things for establishing a secure encrypted session:
Generate an elliptic curve key pair Build session establishment bytes following the ISO-18013-5 specification. Once the key pair is generated, we can use this key pair, the device engagement bytes, and the previously created request data for building the encrypted session establishment bytes. The device engagement bytes are provided automatically inside the onGetMdocRequestData() function by the RP client SDK.
private val secureRepository = SecureRepository() override fun onGetMdocRequestData(deviceEngagementBytes: ByteArray): ByteArray? { val keyPair = secureRepository.generateEcKeyPair() val encryptedSessionEstablishmentBytes = secureRepository.buildSessionEstablishment(requestData, deviceEngagementBytes, keyPair) return encryptedSessionEstablishmentBytes!! } For further information regarding generating the key pair and building the session establishment bytes, check the provided sample code.
Step 2: Initiate a Verification Request with the AppLink
Once the onGetMdocRequestData() function is ready, we can use the request() function to initiate the verification request.
val sessionId = UUID.randomUUID().toString() val WALLET_PACKAGE = "com.samsung.android.spay" rpClientApis.request( WALLET_PACKAGE, sessionId, appLink, OnResponseListener(requestData) ) Processing the Request Response
Once the mDoc request has been sent and processed successfully, the application should receive a ByteArray as response in the onMdocResponse() function inside the listener class. This ByteArray is an encrypted JSON object. Once decrypted, the response should look like the following:
{ "documents": [ { "issuerSigned": { "nameSpaces": { "org.iso.18013.5.1": [ "pGhkaWdlc3RJRBkU-mZyYW5kb21UaGNkNGduZDl5Z2I1cTRjaDV4ZnpxZWxlbWVudElkZW50aWZpZXJrZXhwaXJ5X2RhdGVsZWxlbWVudFZhbHVlwHQyMDMxLTExLTIxVDA3OjAwOjAwWg", "pGhkaWdlc3RJRBknbWZyYW5kb21Udjg1NmsydzIzZzQ3OHk5cTQ0aHJxZWxlbWVudElkZW50aWZpZXJsYWdlX2luX3llYXJzbGVsZW1lbnRWYWx1ZRgr", "pGhkaWdlc3RJRBlvWWZyYW5kb21UbnRtdnJ5OXlucXcyZjY2bmp2NXRxZWxlbWVudElkZW50aWZpZXJqaXNzdWVfZGF0ZWxlbGVtZW50VmFsdWXAdDIwMjMtMTEtMDhUMDc6MDA6MDBa", "pGhkaWdlc3RJRBnXQWZyYW5kb21UOXJqd2NydjZ6cXpqZm1xajNkcnhxZWxlbWVudElkZW50aWZpZXJrZmFtaWx5X25hbWVsZWxlbWVudFZhbHVlZUFoc2Fu" ] }, "issuerAuth": [ "dCBa", { "33": "..." }, "...", "..." ] }, "deviceSigned": {…}, "docType": "org.iso.18013.5.1.mDL" } ], "version": "1.0", "status": 0 } The values inside the org.iso.18013.5.1 JSON Array are the information we requested, in the CBOR (Concise Binary Object Representation) format.
For example, if we decode the value: "pGhkaWdlc3RJRBlvWWZyYW5kb21UbnRtdnJ5OXlucXcyZjY2bmp2NXRxZWxlbWVudElkZW50aWZpZXJqaXNzdWVfZGF0ZWxlbGVtZW50VmFsdWXAdDIwMjMtMTEtMDhUMDc6MDA6MDBa", we find that this CBOR object contains the issue_date field and its value is 2023-11-08T07:00:00.000Z. Similarly, every value provided in the array is a CBOR object that can be decoded using CBOR decoders to find a key-value pair containing the requested information.
We can now receive the mDoc response in the onMdocResponse() function and decode it to retrieve the final requested values:
override fun onMdocResponse(encryptedResponseBytes: ByteArray) { val plainResponse = secureRepository.decryptMdocResponse(encryptedResponseBytes) Log.i(TAG, "plainResponse=${plainResponse?.toPrettyJson()}") val mDocContent = Mdoc18013Utils.parseMdocResponse(plainResponse!!) mDocContent.forEach { (key, value) -> Log.i(TAG, "$key: $value") } } Here, secureRepository.decryptMdocResponse() performs the decryption operation and converts the encrypted bytes into a plain JSON response. Afterwards, the Mdoc18013Utils.parseMdocResponse() function takes the plain response and decodes each CBOR-encoded element contained in the org.iso.18013.5.1 array and returns these values in a simplified dictionary of key-value pairs. If you wish to learn more about these functions, you can check out the provided sample code.
With this step, the sample application's implementation of Verify with Wallet is complete. You can now build and run the application. In the sample application, once the user clicks the "Verify with Samsung Wallet" button, the VWW procedure is initiated. Once the user confirms that they wish to share their information, the application will receive the requested information about the user.
Figure 2: Complete the verification process using VWW
Conclusion
In this article, we have explored how you can integrate the Verify with Wallet RP SDK directly into your application and use it to verify the user's identity. Feel free to integrate the RP SDK in your own application and test the Verify with Samsung Wallet process as well. If you have any further queries regarding this process, feel free to reach out to us through the Samsung Developers Forum.
Related Resources
ISO/IEC 18013-5:2021 - Personal identification — ISO-compliant driving licence — Part 5: Mobile driving licence (mDL) application Mobile Driver License - American Association of Motor Vehicle Administrators - AAMVA RP SDK download link Verify with Wallet API Guidelines Relying Party Card Specifications Sample Code Download Link View the full blog at its source
-
-
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.