MFA Transaction Signing
Enrol a user, set a PIN, and sign a transaction with a hardware-backed key.
Overview
Section titled “Overview”enrollForMfa, setMfaPin and signAction are the Android core’s MFA flow. Read MFA Transaction Signing first for the three-call flow and what the token proves.
Every call has a blocking form and an …Async form taking a callback. The blocking forms do network
I/O, so call them off the main thread.
Basic Usage
Section titled “Basic Usage”Java:
// 1. Once per (user, device), after login. No biometric prompt appears here.AlphynSDK.enrollForMfaAsync(userId, result -> { if (!result.isSuccess()) { Log.w("Byteria", "enrol failed: " + result.getErrorCode()); return; } Log.i("Byteria", "key is in " + result.getSecurityLevel());});
// 2. Set or change the PIN. Verified and hashed ON THE SERVER.char[] newPin = pinField.getText().toString().toCharArray();AlphynSDK.setMfaPinAsync(userId, newPin, null, result -> { /* … */ });
// 3. Per transaction, after the user clears your confirmation screen.String context = "{\"amount\":\"250.00\",\"currency\":\"TRY\",\"toIBAN\":\"TR12…\"}";char[] pin = pinField.getText().toString().toCharArray();
ActionVerificationResult result = AlphynSDK.signAction(userId, "TRANSFER", context, pin);
if (result.isSuccess()) { sendToYourBackend(result.getVerificationToken());}AlphynSDK zeroes every char[] it is handed before returning. Build the array and pass it; do not
keep a String copy of the PIN, because a String cannot be wiped.
Methods
Section titled “Methods”// Enrolmentpublic static MfaResult enrollForMfa(String userId)public static void enrollForMfaAsync(String userId, MfaCallback callback)
// PINpublic static MfaResult setMfaPin(String userId, char[] newPin, char[] oldPin)public static void setMfaPinAsync(String userId, char[] newPin, char[] oldPin, MfaCallback callback)
// Signingpublic static ActionVerificationResult signAction( String userId, String actionLabel, String contextJson, char[] pin)
public static ActionVerificationResult signAction( String userId, String actionLabel, String contextJson, char[] pin, AlphynSubject subject)Parameters
Section titled “Parameters”| Parameter | Type | Notes |
|---|---|---|
userId | String | your identifier for the logged-in user, not one Alphyn issues |
newPin / oldPin | char[] | zeroed by the call. oldPin is null on a first set |
actionLabel | String | your own name for the operation, e.g. TRANSFER |
contextJson | String | the transaction as a JSON object. Its hash is bound into the signature |
pin | char[] | optional. null omits the factor rather than failing |
subject | AlphynSubject | optional. null is byte-identical to the four-argument overload |
MfaResult
Section titled “MfaResult”| Method | Meaning |
|---|---|
isSuccess() | whether the operation succeeded |
getErrorCode() | the backend’s code verbatim, null on success |
getErrorMessage() | human-readable; do not branch on it |
getSecurityLevel() | enrolment only: where the key ended up, e.g. STRONGBOX |
getEnrollmentId() | enrolment only: the backend’s identifier for this enrolment |
ActionVerificationResult
Section titled “ActionVerificationResult”isSuccess(), getVerificationToken(), getExpiresAtMs(), getErrorCode(), getErrorMessage().
Read the token with getVerificationToken(). Never with toString(): that is a debug rendering
and it truncates the token to its first twenty characters, which your backend can only reject.
Declaring an identity
Section titled “Declaring an identity”AlphynSubject subject = AlphynSubject.builder() .tc("10000000146") .phone("+905342223344") .build();
ActionVerificationResult result = AlphynSDK.signAction(userId, "TRANSFER", context, pin, subject);build() never throws and never fails, for any input including null. A field that does not conform
is dropped, never corrected, and a subject in which nothing survived produces a request
byte-identical to one that declared none. getTcHash(), getPhoneHash() and getEmailHash() return
the tokens, or null for a field that was dropped; isEmpty() reports whether anything survived.
The raw values never leave the device. See Subject identity for the accepted forms and for why a token must be treated as identifying data.
Use Cases
Section titled “Use Cases”- Signing a transfer, withdrawal or beneficiary change so your backend can prove afterwards that a specific user’s device authorised those exact values
- Adding a second factor to a high-risk action without an SMS round trip
- Correlating a signed transaction with a person across institutions, through a subject token
Example: signing a transfer end to end
Section titled “Example: signing a transfer end to end”void confirmTransfer(String userId, String iban, String amount, char[] pin) { JSONObject ctx = new JSONObject(); ctx.put("amount", amount); ctx.put("currency", "TRY"); ctx.put("toIBAN", iban);
AlphynSubject subject = AlphynSubject.builder().tc(customerTckn).build();
executor.execute(() -> { ActionVerificationResult result = AlphynSDK.signAction( userId, "TRANSFER", ctx.toString(), pin, subject);
if (!result.isSuccess()) { // THREAT_DETECTED is a refusal, not a transport failure. Do not retry silently. runOnUiThread(() -> showError(result.getErrorCode())); return; }
// Your backend validates this token before it moves anything. yourApi.transfer(iban, amount, result.getVerificationToken()); });}The token is evidence, not a decision. Your backend must validate it server-side and compare the echoed context against the transfer it is about to perform; see Backend verification.