Handling verifier authentication
Establish and inspect how a remote verifier was authenticated so users can decide whether to share credentials with an unauthenticated verifier
Overview
When a verifier requests credentials remotely over OpenID for Verifiable Presentations (OID4VP), your holder application can establish whether the verifier proved its identity before any credential data is shared. This lets you show the user who is asking for their data and how that identity was established, and lets you refuse to proceed with a verifier that cannot be trusted.
In a remote presentation, the verifier signs the authorization request. The Holder SDK validates that signature when it creates the presentation session, and reports how the verifier was authenticated. Your application controls how strict that validation must be, and reads the outcome to inform the user.
Remote and proximity authenticate verifiers differently
This guide covers remote (OID4VP / ISO/IEC 18013-7) verifier authentication, where the verifier signs the authorization request and trust is established when the session is created. Proximity (ISO/IEC 18013-5) presentations authenticate the verifier through a signed device request instead, which ISO/IEC 18013-5 calls mDoc Reader Authentication. The verifierAuthenticationResult described in the proximity guide does not apply to remote presentations; the remote equivalent is the requireTrustedVerifier option and the verifiedBy result described below.
Prerequisites
This guide builds on the Remote Presentation tutorial. Complete that tutorial first so you have a working holder application that can create an online presentation session from an authorization request, then return here to add verifier authentication handling.
Understanding remote verifier authentication
Under ISO/IEC 18013-7:2025, the authorization request is delivered as a signed request object (a JWT) using the x509_san_dns client identifier scheme. The verifier's client_id must be a DNS name that matches a dNSName Subject Alternative Name in the certificate that signed the request. When you create the session, the SDK validates the request signature and then reports how the verifier was authenticated through a verifiedBy result with one of two outcomes:
- Certificate: The request signature was validated against a trusted verifier root certificate configured on the device. This is the stronger outcome, because the verifier's certificate is explicitly trusted ahead of time. The result carries the certificate's common name.
- Domain: The request signature was validated using the verifier's published client metadata, bound to its DNS name through the
x509_san_dnsscheme, rather than against a pre-configured trusted certificate. The result carries theclient_idfrom the request.
You control which of these is acceptable with the requireTrustedVerifier option when you create the session:
- When
requireTrustedVerifierisfalse(the default), the SDK first attempts validation against the stored trusted verifier certificates. If that fails, it falls back to resolving the verifier's client metadata and validating against the keys published there. A successful session is reported as eitherCertificateorDomainaccordingly. - When
requireTrustedVerifieristrue, the SDK only accepts a request that validates against a stored trusted verifier certificate. If no trusted certificate matches, session creation fails rather than falling back to domain validation.
If validation fails, session creation reports an error: validation against a trusted certificate failing surfaces as an "invalid authorization request, verified by certificate" error, and domain validation failing surfaces as an "invalid authorization request, verified by domain" error.
Adding trusted verifier certificates
A Certificate result, and the requireTrustedVerifier option, both depend on trusted verifier root certificates stored on the device. Your application provisions these certificates through the SDK. The same trusted verifier certificate store is used for both remote and proximity presentations.
Certificates are supplied as PEM-encoded or Base64-encoded DER strings. Adding a certificate is idempotent: adding the same certificate again returns the identifier of the existing entry rather than creating a duplicate.
Use addTrustedVerifierCertificates, getTrustedVerifierCertificates, and deleteTrustedVerifierCertificate:
let mobileCredentialHolder = MobileCredentialHolder.shared
// Add one or more trusted verifier certificates (PEM or Base64 DER).
let addedIds = try mobileCredentialHolder.addTrustedVerifierCertificates(
certificates: [verifierCertificatePem]
)
// List the stored trusted verifier certificates.
let stored: [TrustedCertificate] = try mobileCredentialHolder.getTrustedVerifierCertificates()
// Remove one by its identifier.
try mobileCredentialHolder.deleteTrustedVerifierCertificate(certificateId: addedIds[0])Each stored certificate is a TrustedCertificate carrying its id, pem, and commonName.
Use addTrustedVerifierCertificates, getTrustedVerifierCertificates, and deleteTrustedVerifierCertificate:
val mobileCredentialHolder = MobileCredentialHolder.getInstance()
// Add one or more trusted verifier certificates (PEM or Base64 DER).
val addedIds: List<String> = mobileCredentialHolder.addTrustedVerifierCertificates(
listOf(verifierCertificatePem)
)
// List the stored trusted verifier certificates.
val stored: List<TrustedCertificate> = mobileCredentialHolder.getTrustedVerifierCertificates()
// Remove one by its identifier.
mobileCredentialHolder.deleteTrustedVerifierCertificate(addedIds.first())Each stored certificate is a TrustedCertificate carrying its id, pem, and commonName.
Use addTrustedVerifierCertificates, getTrustedVerifierCertificates, and deleteTrustedVerifierCertificate:
import {
addTrustedVerifierCertificates,
getTrustedVerifierCertificates,
deleteTrustedVerifierCertificate,
} from "@mattrglobal/mobile-credential-holder-react-native";
// Add one or more trusted verifier certificates (PEM or Base64 DER).
const result = await addTrustedVerifierCertificates([verifierCertificatePem]);
if (result.isErr()) {
console.error("Failed to add trusted verifier certificates:", result.error);
return;
}
const addedIds = result.value;
// List the stored trusted verifier certificates.
const stored = await getTrustedVerifierCertificates();
// Remove one by its identifier.
await deleteTrustedVerifierCertificate(addedIds[0]);Each stored certificate is a TrustedVerifierCertificate carrying its id, pem, and commonName.
Creating the session and inspecting the result
Create the session as in the Remote Presentation tutorial, choosing the value of requireTrustedVerifier that fits your trust policy, then read verifiedBy from the returned session before presenting the consent UI.
Pass requireTrustedVerifier to createOnlinePresentationSession and switch over the verifiedBy property of the returned OnlinePresentationSession:
func createOnlinePresentationSession(authorizationRequestUri: String) async {
do {
let session = try await mobileCredentialHolder.createOnlinePresentationSession(
authorizationRequestUri: authorizationRequestUri,
requireTrustedVerifier: false
)
switch session.verifiedBy {
case .certificate(let commonName):
// Validated against a trusted verifier root certificate.
print("Verified by certificate: \(commonName)")
case .domain(let clientId):
// Validated against the verifier's published client metadata.
print("Verified by domain: \(clientId)")
}
// Continue with session.matchedCredentials and your consent UI.
} catch MobileCredentialHolderError.invalidAuthorizationRequestVerifiedByCertificate {
// requireTrustedVerifier was true and no trusted certificate matched.
} catch MobileCredentialHolderError.invalidAuthorizationRequestVerifiedByDomain {
// The request could not be validated against the verifier's domain.
} catch {
// Handle other errors
}
}Pass requireTrustedVerifier to createOnlinePresentationSession and match over the verifiedBy property of the returned OnlinePresentationSession:
try {
val session = MobileCredentialHolder.getInstance().createOnlinePresentationSession(
authorizationRequestUri = requestUri,
requireTrustedVerifier = false
)
when (val verifiedBy = session.verifiedBy) {
is VerifiedBy.Certificate ->
// Validated against a trusted verifier root certificate.
Log.i(TAG, "Verified by certificate: ${verifiedBy.commonName}")
is VerifiedBy.Domain ->
// Validated against the verifier's published client metadata.
Log.i(TAG, "Verified by domain: ${verifiedBy.domain}")
}
// Continue with session.getMatchedCredentials() and your consent UI.
} catch (e: HolderException.InvalidAuthorizationRequestVerifiedByCertificateException) {
// requireTrustedVerifier was true and no trusted certificate matched.
} catch (e: Throwable) {
// Handle other errors
}See VerifiedBy for the associated values.
Pass requireTrustedVerifier to createOnlinePresentationSession and read the verifiedBy property of the returned OnlinePresentationSession. The function returns a Result, so the untrusted case is an error value rather than a thrown exception:
const result = await createOnlinePresentationSession({
authorizationRequestUri,
requireTrustedVerifier: false,
});
if (result.isErr()) {
const { error } = result;
if (error.type === MobileCredentialHolderErrorType.InvalidAuthorizationRequestVerifiedByCertificate) {
// requireTrustedVerifier was true and no trusted certificate matched.
}
// Handle other errors
return;
}
const session = result.value;
switch (session.verifiedBy.type) {
case OnlinePresentationSessionVerifiedByType.Certificate:
// Validated against a trusted verifier root certificate.
console.log(`Verified by certificate: ${session.verifiedBy.value}`);
break;
case OnlinePresentationSessionVerifiedByType.Domain:
// Validated against the verifier's published client metadata.
console.log(`Verified by domain: ${session.verifiedBy.value}`);
break;
}
// Continue with session.matchedCredentials and your consent UI.See OnlinePresentationSessionVerifiedBy for the result shape.
What the outcomes mean
The verifiedBy result describes how the verifier's identity was established. What each outcome tells your application:
- Certificate: The request signature was validated against a trusted verifier root certificate stored on the device. The result carries the certificate's common name.
- Domain: The request signature was validated through the verifier's published client metadata, bound to its DNS name through the
x509_san_dnsscheme, rather than against a pre-configured certificate. The result carries theclient_idfrom the request. - Validation failed: Session creation did not succeed, and no session is returned.
How an application uses this is a product and trust-framework decision that the SDK does not make for you. The requireTrustedVerifier option and the verifiedBy result give you two complementary controls, and different implementations combine them differently depending on their ecosystem and risk tolerance. For example, an application might:
- set
requireTrustedVerifiertotrueso that only pre-vetted verifiers can create a session, and domain-only verifiers are rejected at session creation; - leave
requireTrustedVerifierasfalseand accept both outcomes, recording how trust was established for audit; - present the
verifiedByresult to the user and let them decide whether to continue; - proceed automatically for a
Certificateresult while requiring explicit user confirmation for aDomainresult.
Whichever approach you take, you have the outcome before presenting any data, so you can carry it into your flow and respond only for the credentials involved, as shown in the Remote Presentation tutorial.
Domain verification is not the same as a trusted verifier
A Domain result means the request was validly signed and bound to the verifier's DNS name, but it does not mean the verifier has been vetted against a trust framework ahead of time. Whether that distinction matters for a given interaction depends on your ecosystem.
Testing remote verifier authentication
To exercise each outcome:
- Claim a credential in your holder application.
- Present to a verifier whose request is signed by a certificate trusted on the device, and confirm the session is reported as verified by certificate with the expected common name.
- Present to a verifier whose request validates only through its published client metadata, and confirm the session is reported as verified by domain with the expected
client_id. - Set
requireTrustedVerifiertotrueand repeat step 3, confirming that session creation now fails with an "invalid authorization request, verified by certificate" error.
How would you rate this page?
Last updated on