Verify a W3C Verifiable Credential with SpruceKit Mobile SDK
This guide is for iOS applications. Building for Android? Look here: Verify a W3C VC
1. Verify an embedded credential
In this flow, the Verifier will scan a QR-code presented by the Wallet. The QR-Code will directly contain the verifiable presentation encoded as a JWT. As such, the Verifier has to engage the QRCodeScanner component.
import SwiftUI
import SpruceIDMobileSdkRs
// Here is an example View to scan and try verify a W3C VC JWT VP
struct VerifyVCView: View {
@State var success: Bool?
@Binding var path: NavigationPath
var body: some View {
if success == nil {
// Engage the SpruceKit Mobile SDK ScanningComponent
QRCodeScanner(
title: "Scan QR Code",
subtitle: "Looking...",
onRead: onRead: { code in
Task {
do {
try await verifyJwtVp(jwtVp: code)
success = true
} catch {
success = false
print(error)
}
}
},
onCancel: onCancel,
// example font values
titleFont: .customFont(font: .inter, style: .bold, size: .h0),
subtitleFont: .customFont(font: .inter, style: .bold, size: .h4),
cancelButtonFont: .customFont(font: .inter, style: .medium, size: .h3),
readerColor: .white
)
)
} else {
// Plug in your Verifier results view
}
}
}
Verify VCs
Do you know what kind of credential format you are verifying?
import SpruceIDMobileSdkRs
// Verify a W3C VC JWT-VP
_ = try await VerifyJwtVp(jwtVp: vp)
// Verify a W3C VC Barcode
_ = try await VerifyPdf417Barcode(payload: payload)
// Verify a W3C VC Barcode against a Machine Readable Zone
_ = try await VerifyVcbQrcodeAgainstMrz(mrzPayload: mrz, qrPayload: vcb)
The steps as explained above allow you to execute a verification offline by scanning a credential embedded in the QR code. You can also verify online with OID4VP, using the DelegatedVerifer component. The DelegatedVerifier allows your Mobile App Verifier to rely on your own web service to handle a PresentationRequest, and receive and validate the response. The Mobile App will then only receive an outcome back from your web service.
2. Delegated Verifier
In the delegated flow, the Verifier generates a QR-code to advertise the PresentationRequest to the Wallet. To use the DelegateVerifier, you will need a back end service that executes the OID4VP transaction. From your mobile application, you will only advertise the request and poll the status. To manage the UI during the verification, you will have to manage the DelegatedVerifierStatus. You can monitor that state, like in the example below, and manage your UI based on the state.
// Define your DelegatedVerifier View
struct VerifyDelegatedOid4vpView: View {
@Binding var path: NavigationPath
var verificationId: Int64
var verificationMethod: VerificationMethod?
var url: URL?
var baseUrl: String
@State var step = VerifyDelegatedOid4vpViewSteps.loadingQrCode
@State var status = DelegatedVerifierStatus.initiated
@State var loading: String? = nil
@State var errorTitle: String? = nil
@State var errorDescription: String? = nil
@State var verifier: DelegatedVerifier? = nil
@State var authQuery: String? = nil
@State var uri: String? = nil
@State var presentation: String? = nil
init(path: Binding<NavigationPath>, verificationId: Int64) {
self._path = path
self.verificationId = verificationId
do {
// Verification method from db
verificationMethod = try VerificationMethodDataStore
.shared
.getVerificationMethod(rowId: verificationId)
.unwrap()
// Verification method base url
url = URL(string: verificationMethod!.url)
let unwrappedUrl = try url.unwrap()
baseUrl = unwrappedUrl
.absoluteString
.replacingOccurrences(of: unwrappedUrl.path(), with: "")
} catch {
self.errorTitle = "Failed Initializing"
self.errorDescription = error.localizedDescription
self.verificationMethod = nil
self.url = URL(string: "")
self.baseUrl = ""
}
}
func monitorStatus(status: DelegatedVerifierStatus) async {
do {
let res = try await verifier?.pollVerificationStatus(url: "\(uri.unwrap())?status=\(status)")
if let newStatus = res?.status {
switch newStatus {
// Manage your DelegatedVerifierStatus here
}
} else {
// if can't find res.status, call monitorStatus
// with the same parameters
await monitorStatus(status: status)
}
} catch {
errorTitle = "Error Verifying Credential"
errorDescription = error.localizedDescription
}
}
func initiateVerification() {
Task {
do {
let unwrappedUrl = try url.unwrap()
// Delegated Verifier
verifier = try await DelegatedVerifier.newClient(baseUrl: baseUrl)
// Get initial parameters to delegate verification
let delegatedVerificationUrl = "\(unwrappedUrl.path())?\(unwrappedUrl.query() ?? "")"
let delegatedInitializationResponse = try await verifier
.unwrap()
.requestDelegatedVerification(url: delegatedVerificationUrl)
authQuery = "openid4vp://?\(delegatedInitializationResponse.authQuery)"
uri = delegatedInitializationResponse.uri
// Display QR Code
step = VerifyDelegatedOid4vpViewSteps.presentingQrCode
// Call method to start monitoring status
await monitorStatus(status: status)
} catch {
errorTitle = "Failed getting QR Code"
errorDescription = error.localizedDescription
}
}
}
func onBack() {
while !path.isEmpty {
path.removeLast()
}
}
var body: some View {
ZStack {
if errorTitle != nil && errorDescription != nil {
ErrorView(
errorTitle: errorTitle!,
errorDetails: errorDescription!,
onClose: onBack
)
} else {
switch step {
// Manage your VerifyDelegatedOid4vpViewSteps here
}
}
}
.navigationBarBackButtonHidden(true)
.onAppear(perform: {
initiateVerification()
})
}
}