In a world increasingly dependent on digital interactions, identity verification must evolve to become more secure, private, and user-controlled. Standards like SD-JWT (Selective Disclosure JSON Web Token), OIDC4VCI (OpenID Connect for Verifiable Credential Issuance), and OIDC4VP (OpenID Connect for Verifiable Presentations) empower individuals to disclose only the necessary parts of their digital credentials in a consented and verifiable manner. This article explores how these standards work together, with hands-on implementation examples using Spring Boot as the credential issuer and Android (both Jetpack Compose and XML UI) as the wallet app.

Understanding the Key Concepts

SD-JWT (Selective Disclosure JWT)

SD-JWT allows selective disclosure of credential attributes. Instead of sharing a full credential (e.g., an entire digital passport), users can disclose only what’s necessary (e.g., age and nationality). Each claim is hashed and stored in the JWT, and the corresponding clear-text values are revealed only upon presentation with user consent.

OIDC4VCI (OpenID Connect for Verifiable Credential Issuance)

OIDC4VCI extends OpenID Connect to securely issue Verifiable Credentials (VCs). It involves:

  • Credential Offer and Issuance initiation

  • User authentication (with consent)

  • Delivery of the VC in SD-JWT format

OIDC4VP (OpenID Connect for Verifiable Presentation)

OIDC4VP extends OpenID Connect to allow users to present VCs to verifiers using selective disclosure. It ensures:

  • The verifier gets only what it requests

  • Users approve the data they share

  • Claims are verified cryptographically

Why Selective Disclosure Matters

Traditional identity systems expose too much personal data. Selective disclosure lets users maintain privacy while still satisfying verification needs. It:

  • Minimizes data exposure

  • Reduces tracking risk

  • Complies with data protection laws like GDPR

Architecture Overview

  1. Issuer (Spring Boot): Issues SD-JWT-based credentials over OIDC4VCI.

  2. Wallet (Android App): Stores credentials and presents them on request using OIDC4VP.

  3. Verifier: Requests specific claims and verifies their authenticity.

Implementing OIDC4VCI in Spring Boot

Setup Dependencies

Add dependencies to your pom.xml or build.gradle for:

  • Spring Web

  • OAuth2 Authorization Server

  • Nimbus JOSE JWT

xml
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.37</version>
</dependency>

Credential Issuance Endpoint

java
@RestController
@RequestMapping("/oidc/credential")
public class CredentialController {
@PostMapping(“/issue”)
public ResponseEntity<?> issueCredential(@RequestBody CredentialRequest request) {
String sdJwt = generateSDJWT(request);
return ResponseEntity.ok(Map.of(“credential”, sdJwt));
}private String generateSDJWT(CredentialRequest request) {
// Construct SD-JWT with hash-disclosed claims
Map<String, String> disclosedClaims = Map.of(
“given_name”, “Alice”,
“family_name”, “Doe”,
“birthdate”, “1998-04-20”
);List<String> disclosures = new ArrayList<>();
Map<String, String> digests = new HashMap<>();for (Map.Entry<String, String> entry : disclosedClaims.entrySet()) {
String json = String.format(“[\”%s\”,\”%s\”]”, entry.getKey(), entry.getValue());
String digest = Base64.getUrlEncoder().withoutPadding()
.encodeToString(MessageDigest.getInstance(“SHA-256”).digest(json.getBytes()));
disclosures.add(Base64.getUrlEncoder().encodeToString(json.getBytes()));
digests.put(entry.getKey(), digest);
}JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer(“https://issuer.example.com”)
.claim(“_sd”, new ArrayList<>(digests.values()))
.issueTime(new Date())
.build();JWSHeader header = new JWSHeader(JWSAlgorithm.ES256);
SignedJWT jwt = new SignedJWT(header, claims);
jwt.sign(new ECDSASigner(loadPrivateKey()));return jwt.serialize() + “~” + String.join(“~”, disclosures);
}
}

OIDC4VP on Android

Gradle Dependencies

groovy
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.auth0.android:jwtdecode:2.0.1'
implementation 'androidx.security:security-crypto:1.1.0-alpha03'

Jetpack Compose UI

kotlin
@Composable
fun CredentialPresentationScreen(viewModel: CredentialViewModel) {
val context = LocalContext.current
val credential by viewModel.credential.collectAsState()
Column(modifier = Modifier.padding(16.dp)) {
Text(“Present Credential”, fontSize = 20.sp)
credential?.let {
Text(“Name: ${it.givenName}“)
Text(“Birthdate: ${it.birthdate}“)
}
Button(onClick = {
viewModel.presentCredential {
Toast.makeText(context, “Presented!”, Toast.LENGTH_SHORT).show()
}
}) {
Text(“Present Selectively”)
}
}
}

XML UI

xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id=“@+id/credential_name”
android:textSize=“18sp”
android:text=“Name: Alice” /><Button
android:id=“@+id/present_button”
android:text=“Present Selectively”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
</LinearLayout>

Kotlin Logic to Handle SD-JWT

kotlin
fun parseSDJWT(sdJwt: String): Map<String, String> {
val parts = sdJwt.split("~")
val jwt = parts[0]
val disclosures = parts.subList(1, parts.size)
val decodedJWT = JWT(jwt)
val expectedDigests = decodedJWT.getClaim(“_sd”).asList(String::class.java)return disclosures.mapNotNull { disclosure ->
val json = String(Base64.getDecoder().decode(disclosure))
val digest = Base64.getUrlEncoder().withoutPadding()
.encodeToString(MessageDigest.getInstance(“SHA-256”).digest(json.toByteArray()))
if (digest in expectedDigests) {
val (key, value) = Regex(“\\[\”(.*?)\”,\”(.*?)\”]”).find(json)!!.destructured
key to value
} else null
}.toMap()
}

Security and Privacy Considerations

  • Always sign SD-JWTs with strong cryptographic keys.

  • Store credentials encrypted on device using Android Keystore.

  • Prompt for user consent on each credential presentation.

  • Use short-lived presentation tokens to prevent replay attacks.

  • Comply with GDPR principles (e.g., data minimization, purpose limitation).

Conclusion

As the digital world grows more interconnected and the need for robust, privacy-preserving identity systems becomes increasingly critical, the convergence of SD-JWTs, OIDC4VCI, and OIDC4VP represents a pivotal step forward in how we think about identity, trust, and user autonomy.

Traditional identity verification methods are often intrusive, inflexible, and inconsistent with modern privacy expectations. Users are typically forced to disclose excessive information, leading to data overexposure, potential identity theft, and non-compliance with regulatory frameworks like GDPR and CCPA. In contrast, selective disclosure, as enabled by SD-JWTs, allows users to share only the minimal necessary data while retaining cryptographic verifiability—effectively achieving both data minimization and integrity.

The OIDC4VCI protocol plays a crucial role in making Verifiable Credentials issuance scalable and interoperable across platforms. By extending the well-understood OpenID Connect standard, it leverages existing OAuth2-based infrastructures while adding the semantics and payloads required for digital credential issuance. When used in conjunction with Spring Boot, developers can quickly deploy credential issuers that are secure, standards-compliant, and extensible—capable of serving healthcare providers, educational institutions, government agencies, and financial services with verifiable digital identity issuance workflows.

On the client side, OIDC4VP provides a similarly extensible and secure method for presenting credentials. Users are no longer passive participants in identity checks—they are active actors who can review, approve, and tailor their disclosures to specific verifiers and contexts. Integrating this protocol into an Android wallet application empowers developers to build smart, user-controlled identity apps. Whether it’s using Jetpack Compose for modern UIs or XML layouts for legacy support, Android provides the flexibility to present credentials in a secure and user-friendly way.

Imagine a university student needing to prove enrollment to access student discounts—now, instead of presenting a full transcript or ID card, they can selectively disclose their status. Or consider a patient interacting with a telemedicine app that only needs confirmation of their insurance provider. These are real scenarios where selective disclosure dramatically reduces friction and risk while enhancing trust between parties.

The technical implementation also highlights a key advantage of using this protocol suite: modularity and standards compliance. Developers can adopt and implement these protocols incrementally—adding SD-JWT support first, then layering in OIDC4VCI endpoints, and finally integrating OIDC4VP flows into mobile wallets or identity verifiers. This modular adoption strategy allows teams to prioritize use cases and roll out improvements progressively, without overhauling entire systems.

Beyond individual use cases, the broader impact of these technologies is systemic. They align with the growing momentum behind self-sovereign identity (SSI) and decentralized identity (DID) frameworks, helping to decentralize identity authority and return data control to users. In doing so, they also help organizations align with privacy regulations, avoid vendor lock-in, and prepare for future digital trust ecosystems.

Security remains a top priority throughout this approach. SD-JWTs ensure integrity through digital signatures. Presentations are time-bound and consented. The Android client code examples illustrate best practices like hashing, base64 encoding, and secure keystore usage. When combined with encrypted data storage and biometric access controls, mobile apps can become powerful, secure credential wallets.

In summary, this article has shown how SD-JWTs, OIDC4VCI, and OIDC4VP together form a powerful trio to implement user-consented, privacy-preserving digital identity systems. Using Spring Boot, developers can rapidly stand up credential issuers that issue verifiable, selectively disclosable credentials. Android apps, through Compose or XML-based interfaces, can present those credentials efficiently and securely. The combination unlocks a wide range of high-impact use cases, from government services to banking, education, and healthcare.

Adopting these open standards doesn’t just enable better identity verification—it fosters a more equitable, transparent, and secure digital society. The future of identity is user-centric, and with these tools, we’re already well on our way.