DPoP (device-bound tokens)
DPoP (RFC 9449) binds a session to a device key: each request carries a signed proof that only the holder of the key can make. Binding is optional at Aduki ID.
val key = Software.p256() // pro.aduki.crypto.dpop.Software
val client = Aduki.login(handle, password, code = code, dpop = key)
// or
val client = Aduki.builder().token(token).dpop(key).build()
Keys
pro.aduki.crypto.dpop.Key is a sign-only interface:
interface Key {
val alg: String // "ES256" or "EdDSA"
val jwk: Map<String, String> // public members only, never `d`
fun sign(input: ByteArray): ByteArray
fun thumbprint(): String // RFC 7638, stored by Aduki ID as cnf.jkt
}
Software.p256() (ES256) and Software.ed25519() (EdDSA) generate keys in
process memory using the JDK. Hardware keystores generate P-256 keys, so use ES256 for hardware-backed keys.
On Android, use pro.aduki.android.keystore.AndroidKey from the android module
(pro.aduki:android), which implements Key using the Android Keystore.
What the SDK sends
pro.aduki.net.http.Dpop is an OkHttp interceptor installed by
builder().dpop(key) and login(dpop = key):
- A request with no
Authorization(sign-in, refresh) carries a proof with no access-token hash. Aduki ID binds the session to the key. - A request whose access token is bound to this key (its
cnf.jktequals the key’s thumbprint) is sent asAuthorization: DPoP <token>with a proof carryingath, the SHA-256 hash of the token. - Other credentials (API keys, unbound tokens) are left unchanged.
- Each proof has
htm,htu(without query or fragment),iat, a fresh randomjti, andnoncewhen the server asked for one. - A
DPoP-Noncefrom the server is remembered per host; a401or400carrying a new nonce is retried once with it.
The private key is never read; Key only signs.
Android device key
On Android use pro.aduki.android.keystore.AndroidKey as the Key: a non-extractable P-256 Keystore key (ES256, JWS r || s signatures). Aduki ID’s gate::dpop accepts ES256. Verified on an Android 14 emulator (software-backed): proofs verify with the public JWK. Hardware backing is reported by AndroidKey.protection() and not verified here.