क्रेडेंशियल ट्रांसफ़र करना

क्रेडेंशियल मैनेजर के क्रेडेंशियल ट्रांसफ़र एपीआई की मदद से, क्रेडेंशियल उपलब्ध कराने वाली कंपनियों के बीच उपयोगकर्ता के क्रेडेंशियल को सुरक्षित तरीके से और उसी डिवाइस पर ट्रांसफ़र किया जा सकता है. इस गाइड में बताया गया है कि Android पर क्रेडेंशियल उपलब्ध कराने वाली कंपनियां, androidx.credentials:providerevents लाइब्रेरी के ज़रिए उपलब्ध कराए गए एपीआई के साथ कैसे इंटिग्रेट कर सकती हैं. यह सुविधा, स्टैंडर्ड FIDO क्रेडेंशियल एक्सचेंज फ़ॉर्मैट (सीएक्सएफ़) का इस्तेमाल करके, पासवर्ड, पासकी, पते की जानकारी, और कस्टम फ़ील्ड के साथ काम करती है.

सबसे ज़रूरी सिद्धांत

क्रेडेंशियल ट्रांसफ़र फ़्रेमवर्क, एक ही डिवाइस पर पीयर-टू-पीयर क्रेडेंशियल ट्रांसफ़र की सुविधा देता है. इससे Android OS या बिना पुष्टि वाले ऐप्लिकेशन को रॉ क्रेडेंशियल नहीं दिखते.

फ़्रेमवर्क में दो मुख्य भूमिकाएं तय की गई हैं:

  • एक्सपोर्टर (सोर्स उपलब्ध कराने वाला): क्रेडेंशियल उपलब्ध कराने वाला ऐसा प्लैटफ़ॉर्म जिसके पास फ़िलहाल उपयोगकर्ता के क्रेडेंशियल हैं. यह सिस्टम के साथ, एक्सपोर्ट किए जा सकने वाले उपलब्ध खातों (ExportEntry) के मेटाडेटा को पहले से रजिस्टर करता है. साथ ही, उपयोगकर्ता के चुने जाने पर ट्रांसफ़र के अनुरोधों का जवाब देता है.
  • इंपोर्टर (क्रेडेंशियल देने वाली कंपनी या सेटअप विज़र्ड): क्रेडेंशियल देने वाली कंपनी या सेटअप विज़र्ड, जो इंपोर्ट करने का अनुरोध (ImportCredentialsRequest) शुरू करता है. इसमें यह जानकारी होती है कि वह किस तरह के क्रेडेंशियल और एक्सटेंशन पा सकता है.

Android वर्शन के साथ काम करने की सुविधा

Credentials Transfer API, Android 8 (एपीआई लेवल 26) और इसके बाद के वर्शन वाले डिवाइसों पर काम करता है.

डिपेंडेंसी जोड़ें

अपने मॉड्यूल की build.gradle या build.gradle.kts में, androidx.credentials:providerevents डिपेंडेंसी जोड़ें:

dependencies {
    implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}

ज़रूरी क्लास को इंस्टैंशिएट करना

ज़रूरत के मुताबिक ProviderEventsManager का इंस्टेंस बनाएं.

val providerEventsManager = ProviderEventsManager.create(context)

एक्सपोर्टर को लागू करना

अगर आपको लोगों को अपने ऐप्लिकेशन से, डिवाइस पर क्रेडेंशियल सेव करने की सुविधा देने वाली अन्य कंपनियों के ऐप्लिकेशन में क्रेडेंशियल एक्सपोर्ट करने की अनुमति देनी है, तो एक्सपोर्टर की भूमिका लागू करें.

एक्सपोर्ट एंट्री रजिस्टर करना

जब आपका क्रेडेंशियल देने वाला प्लैटफ़ॉर्म बदलता है, तब ProviderEventsManager.registerExport() का इस्तेमाल करके अपने ExportEntry आइटम रजिस्टर करें या उन्हें अपडेट करें. उदाहरण के लिए, जब कोई उपयोगकर्ता साइन इन करता है, क्रेडेंशियल जोड़ता है या खातों में बदलाव करता है.

हर ExportEntry के लिए ये ज़रूरी हैं:

  • id: यह एक सीक्रेट आइडेंटिफ़ायर है. इसे बिना किसी क्रम के जनरेट किया जाता है. यह एक्सपोर्ट की गई इस एंट्री को यूनीक तरीके से दिखाता है. आपको इस आईडी को सुरक्षित रखना होगा. आपको इसकी ज़रूरत बाद में, ट्रांसफ़र करने के अनुरोधों की पुष्टि करने के लिए पड़ेगी.
  • accountDisplayName: खाता लेबल (उदाहरण के लिए, "Personal Account"). यह ज़रूरी नहीं है.
  • userDisplayName: उपयोगकर्ता का प्राइमरी आइडेंटिफ़ायर (उदाहरण के लिए, "alice@example.com").
  • icon: यह Bitmap आइकॉन, सेवा देने वाली कंपनी या खाते को दिखाता है. इसे लाइब्रेरी अपने-आप 32x32 PNG में बदल देती है.
  • supportedCredentialTypes: CredentialTypes से स्ट्रिंग कॉन्स्टेंट का एक सेट. इससे पता चलता है कि इस एंट्री में किस तरह का डेटा मौजूद है.

suspend fun registerMyProviderForExport(
    providerEventsManager: ProviderEventsManager,
    providerIcon: Bitmap,
    // Randomly generated and stored in encrypted storage
    secretEntryId: String
) {
    val entry = ExportEntry(
        id = secretEntryId,
        accountDisplayName = "MyProvider Personal",
        userDisplayName = "alice@example.com",
        icon = providerIcon,
        supportedCredentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD
        )
    )

    // RegisterExportRequest.create() attaches the default WASM matcher from assets
    val request = RegisterExportRequest.create(context, listOf(entry))

    try {
        val response = providerEventsManager.registerExport(request)
        // Registration successful
    } catch (e: Exception) {
        // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException)
    }
}

मेनिफ़ेस्ट फ़ाइल में एक्सपोर्टर गतिविधि के बारे में बताएं

जब उपयोगकर्ता, सिस्टम सिलेक्टर यूज़र इंटरफ़ेस (यूआई) पर मौजूद आपके ExportEntry को चुनता है, तो सिस्टम आपके तय किए गए Activity को लॉन्च करता है. इस ऐक्टिविटी को ज़रूरी इंटेंट कार्रवाई और कॉन्टेंट यूआरआई स्कीम के साथ एलान करें.

अनधिकृत इनवोकेशन और कंफ्यूज़्ड-डिप्टी हमलों से बचने के लिए, Android 14 (एपीआई लेवल 34) और इसके बाद के वर्शन पर android:requireContentUriPermissionFromCaller="write" का एलान करें. इससे Android ऑपरेटिंग सिस्टम को यह निर्देश मिलता है कि वह यह पुष्टि करे कि लॉन्च करने का अनुरोध करने वाले कॉलर के पास, डेस्टिनेशन कॉन्टेंट यूआरआई में लिखने की अनुमतियां हैं या नहीं. इसके बाद ही, गतिविधि शुरू करने की अनुमति दी जाती है. इसे इस उदाहरण में दिखाया गया है:

<activity
    android:name="com.example.CredentialExportActivity"
    android:exported="true"
    android:requireContentUriPermissionFromCaller="write"
    android:label="@string/export_activity_label">
    <intent-filter>
        <!-- This intent action is required for Credential Manager to invoke this activity -->
        <action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="content" />
    </intent-filter>
</activity>

अपनी गतिविधि में ट्रांसफ़र इंटेंट को मैनेज करना

अपने CredentialExportActivity में, अनुरोध को पार्स करने के लिए IntentHandler.retrieveProviderImportCredentialsRequest(intent) का इस्तेमाल करें. साथ ही, कॉल करने वाले कॉम्पोनेंट और अनुरोध के मेटाडेटा की पुष्टि करें. इसके अलावा, ज़रूरी बायोमेट्रिक ऑथेंटिकेशन करें और दिए गए कॉन्टेंट यूआरआई में FIDO CXF पेलोड वापस लिखें.

अनुरोध की पुष्टि करना

आपकी गतिविधि एक्सपोर्ट की जाती है. इसलिए, क्रेडेंशियल एक्सपोर्ट करने के फ़्लो को सुरक्षित रखें. इसके लिए, यहां दी गई हर लेयर की पुष्टि करें:

  1. credId सेशन नॉन्स की पुष्टि करें (सुरक्षा का मुख्य तरीका): अपने ExportEntry को रजिस्टर करते समय, क्रिप्टोग्राफ़िक तरीके से रैंडम, हाई-एंट्रॉपी id (नॉन्स) जनरेट करें और इसे कम समय के लिए चलने वाले सेशन से जोड़ें. अपनी गतिविधि में, पुष्टि करें कि request.credId, आपके सेवा देने वाले व्यक्ति या कंपनी की ओर से पहले जारी किए गए, चालू और समयसीमा खत्म न हुए सेशन नॉन्स से मेल खाता हो. तीसरे पक्ष के ऐप्लिकेशन इस नॉनस का अनुमान नहीं लगा सकते. इससे यह पक्का होता है कि सिस्टम सिलेक्टर पर, उपयोगकर्ता ने सही विकल्प चुना है.
  2. पुष्टि करें Activity.getCallingPackage() (कॉलर की जांच): क्रेडेंशियल सिलेक्टर, startActivityForResult का इस्तेमाल करके आपकी गतिविधि लॉन्च करता है. इसलिए, आपकी गतिविधि getCallingPackage() की जांच कर सकती है. इससे यह पुष्टि की जा सकती है कि यूज़र इंटरफ़ेस (यूआई) लॉन्च करने वाला कॉलर, क्रेडेंशियल सिलेक्टर का भरोसेमंद सिस्टम फ़्रेमवर्क है. यह फ़्रेमवर्क, Google Play services वाले Android डिवाइसों पर "com.google.android.gms" होता है. अगर कॉलिंग पैकेज की पहचान नहीं हो पाती है, तो अनुरोध अस्वीकार करें.
  3. request.callingAppInfo की जांच करें (टारगेट ऐप्लिकेशन की जानकारी): callingAppInfo उस डेस्टिनेशन ऐप्लिकेशन की पहचान करता है जिसे इंपोर्ट किए गए क्रेडेंशियल मिलेंगे. इस जानकारी का इस्तेमाल, पारदर्शिता के लिए किया जा सकता है. जैसे, पुष्टि करने वाले यूज़र इंटरफ़ेस (यूआई) में डेस्टिनेशन ऐप्लिकेशन का नाम और आइकॉन दिखाना. इसके अलावा, यह पुष्टि करने के लिए भी इसका इस्तेमाल किया जा सकता है कि डेस्टिनेशन ऐप्लिकेशन भरोसेमंद है या नहीं.

class CredentialExportActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 1. Extract the transfer request from the incoming Intent
        val request: ProviderImportCredentialsRequest? =
            IntentHandler.retrieveProviderImportCredentialsRequest(intent)

        if (request == null) {
            finishWithError()
            return
        }

        // 2. Validate CallingAppInfo and secret `credId`
        val callingAppPackage = request.callingAppInfo.packageName
        val receivedCredId = request.credId
        if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) {
            // Secret ID mismatch or untrusted caller -> abort
            sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request"))
            return
        }

        // 3. Optional: Prompt user for Biometric / PIN authentication before exporting
        authenticateUserThenExport(request)
    }

    private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) {
        // ... Biometric prompt logic ...
        // Once authenticated, generate the FIDO CXF JSON string matching the requested types
        val cxfJsonPayload = buildFidoCxfJsonPayload(
            requestedTypes = request.request.credentialTypes,
            requestedExtensions = request.request.knownExtensions
        )

        val response = ImportCredentialsResponse(cxfJsonPayload)

        // 4. Write the JSON payload to the Content URI and set Activity result
        IntentHandler.setImportCredentialsResponse(
            context = this,
            uri = request.uri,
            intent = intent,
            response = response
        )
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) {
        IntentHandler.setImportCredentialsException(intent, exception)
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun verifySecretEntryId(credentialId: String): Boolean {
        // Check if credentialId matches what you stored when calling RegisterExportRequest
        return credentialId == getStoredSecretEntryId()
    }

    private fun isTrustedImporter(packageName: String): Boolean {
        // Implement any specific allowlisting / caller checks if required
        return true
    }

    private fun finishWithError() {
        setResult(Activity.RESULT_CANCELED)
        finish()
    }
}

रजिस्ट्रेशन और क्लियरिंग से जुड़ी गड़बड़ियों को एक्सपोर्ट करना

इस मॉड्यूल में मौजूद सभी अपवाद, ImportCredentialsException, RegisterExportException या ClearExportException के सबक्लास हैं.

  • RegisterExportProviderConfigurationException या ClearExportProviderConfigurationException: यह तब दिखता है, जब सेवा देने वाली कंपनी के सेटअप से जुड़ी समस्याओं की वजह से, रजिस्ट्रेशन या डेटा मिटाने की प्रोसेस पूरी नहीं हो पाती. उदाहरण के लिए, ExportEntry में supportedCredentialTypes खाली है या ओएस के ऐसे टियर पर कॉल किया जा रहा है जो काम नहीं करता.
  • RegisterExportUnknownErrorException या ClearExportUnknownErrorException: एक्सपोर्ट रजिस्ट्री को अपडेट करते समय, सिस्टम या स्टोरेज से जुड़ी कोई गड़बड़ी हुई है.

इंपोर्टर को लागू करना

अपने ऐप्लिकेशन में क्रेडेंशियल इंपोर्ट करने के लिए, इंपोर्टर की भूमिका लागू करें. उदाहरण के लिए, ऑनबोर्डिंग या सेवा देने वाली कंपनी के क्रेडेंशियल इंपोर्ट करने के दौरान. इसके बाद, ProviderEventsManager.importCredentials() को कॉल करके, इंपोर्ट करने की प्रोसेस शुरू करें.

इंपोर्ट करने का अनुरोध तैयार करना और फ़्लो लॉन्च करना

यह बताएं कि आपका इंपोर्टर, कौनसे CredentialTypes और KnownExtensions फ़ॉर्मैट इस्तेमाल करता है:

suspend fun startCredentialImport(
    activityContext: Context,
    providerEventsManager: ProviderEventsManager
) {
    val importRequest = ImportCredentialsRequest(
        credentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH,
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY,
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_NOTE
        ),
        knownExtensions = setOf(
            KnownExtensions.KNOWN_EXTENSION_SHARED
        )
    )

    try {
        // Launches the system Selector UI; suspends until user selects a provider and completes transfer
        val response = providerEventsManager.importCredentials(activityContext, importRequest)

        // 1. Inspect the source exporter's package info
        val exporterPackageName = response.callingAppInfo.packageName

        // 2. Parse the FIDO CXF JSON string
        val cxfJsonString = response.response.responseJson
        parseAndSaveImportedCredentials(cxfJsonString)
    } catch (e: ImportCredentialsException) {
        // Handle specific import exceptions (e.g., ImportCredentialsCancellationException)
        handleImportFailure(e)
    }
}

private fun parseAndSaveImportedCredentials(cxfJsonString: String) {
    val rootJson = JSONObject(cxfJsonString)
    // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification:
    // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html
}

// Helper function to make it compile
private fun handleImportFailure(e: ImportCredentialsException) {}

फ़्लो में होने वाली गड़बड़ियों को इंपोर्ट करना

इस मॉड्यूल में मौजूद सभी अपवाद, ImportCredentialsException, RegisterExportException या ClearExportException के सबक्लास हैं.

  • ImportCredentialsCancellationException: उपयोगकर्ता ने सिलेक्टर यूज़र इंटरफ़ेस (यूआई) को खारिज कर दिया या एक्सपोर्ट करने की गतिविधि (Activity.RESULT_CANCELED) को रद्द कर दिया.
  • ImportCredentialsNoExportOptionException: एक्सपोर्ट के लिए रजिस्टर की गई किसी भी एंट्री में, क्रेडेंशियल के अनुरोध किए गए टाइप से मेल नहीं खाया. इसके अलावा, एक्सपोर्ट करने वाले व्यक्ति या कंपनी ने अनुरोध अस्वीकार कर दिया.
  • ImportCredentialsProviderConfigurationException: कॉन्फ़िगरेशन में गड़बड़ी (उदाहरण के लिए, ImportCredentialsRequest में खाली credentialTypes सेट किया गया है या अनुमतियां मौजूद नहीं हैं).
  • ImportCredentialsInvalidJsonException: एक्सपोर्टर ने गलत फ़ॉर्मैट वाला या खाली JSON पेलोड भेजा है. इसलिए, अनुरोध की पुष्टि नहीं हो सकी.
  • ImportCredentialsSystemErrorException: इंपोर्ट के दौरान, Android सिस्टम या Binder ट्रांसफ़र में गड़बड़ी हुई.
  • ImportCredentialsUnknownCallerException: फ़्रेमवर्क, कॉल करने वाले ऐप्लिकेशन की पुष्टि नहीं कर सका.
  • ImportCredentialsUnknownErrorException: इंपोर्ट के दौरान, गड़बड़ी का पता नहीं चला या अचानक कोई गड़बड़ी हुई.

क्रेडेंशियल के इस्तेमाल किए जा सकने वाले टाइप और एक्सटेंशन

androidx.credentials.providerevents.transfer.CredentialTypes ऑब्जेक्ट, FIDO CXF आइटम टाइप के हिसाब से स्टैंडर्ड स्ट्रिंग कॉन्स्टेंट तय करता है:

कॉन्स्टेंट वैल्यू (cxf टाइप) ब्यौरा
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" उपयोगकर्ता नाम और पासवर्ड के लॉग इन क्रेडेंशियल.
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" FIDO2 या WebAuthn पासकी के सार्वजनिक कुंजी क्रेडेंशियल.
CREDENTIAL_TYPE_ADDRESS "address" फ़ॉर्म में अपने-आप भरने की सुविधा के लिए, डाक या शिपिंग पते की जानकारी.
CREDENTIAL_TYPE_API_KEY "api-key" एपीआई ऐक्सेस पासकोड और टोकन.
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" क्रेडिट और डेबिट कार्ड से किए गए पेमेंट की जानकारी.
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" कस्टम ग्रुपिंग या उपयोगकर्ता के तय किए गए फ़ील्ड.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" ड्राइविंग लाइसेंस की जानकारी.
CREDENTIAL_TYPE_FILE "file" बाइनरी फ़ाइलों के लिए मेटाडेटा और प्लेसहोल्डर रेफ़रंस.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" मशीन से जनरेट किए गए सुरक्षित पासवर्ड.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" नैशनल आईडी कार्ड, एसएसएन, टीआईएन या पासपोर्ट के रेफ़रंस.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" लॉजिकल लिंक, जो पेलोड में मौजूद किसी दूसरे आइटम की ओर ले जाते हैं.
CREDENTIAL_TYPE_NOTE "note" उपयोगकर्ता के तय किए गए सुरक्षित नोट (UTF-8 स्ट्रिंग).
CREDENTIAL_TYPE_PASSPORT "passport" पासपोर्ट और यात्रा से जुड़े दस्तावेज़ की जानकारी.
CREDENTIAL_TYPE_PERSON_NAME "person-name" व्यक्ति की पहचान और नाम की जानकारी.
CREDENTIAL_TYPE_SSH_KEY "ssh-key" एसएसएच सार्वजनिक और निजी कुंजी के जोड़े.
CREDENTIAL_TYPE_TOTP "totp" समय के हिसाब से एक बार इस्तेमाल होने वाले पासवर्ड (2FA) के सीक्रेट.
CREDENTIAL_TYPE_WIFI "wifi" वाई-फ़ाई नेटवर्क का SSID और पासफ़्रेज़.

कस्टम WASM (WebAssembly) मैच करने वाले टूल (ऐडवांस)

डिफ़ॉल्ट रूप से, RegisterExportRequest.create(context, entries) कॉल करने पर, लाइब्रेरी ऐसेट से सिस्टम-डिफ़ॉल्ट credential_transfer_matcher.wasm बंडल हो जाता है. इससे सिर्फ़ supportedCredentialTypes के इंटरसेक्शन के आधार पर एंट्री फ़िल्टर होती हैं.

अगर क्रेडेंशियल देने वाली कंपनी को मैचिंग के लिए जटिल लॉजिक की ज़रूरत होती है (उदाहरण के लिए, डाइनैमिक क्षमता की जांच या कस्टम फ़ील्ड के आधार पर शर्त के साथ फ़िल्टर करना), तो WASM क्रेडेंशियल ट्रांसफ़र करने वाले एपीआई से मेल खाने वाला अपना WebAssembly मॉड्यूल कंपाइल किया जा सकता है. साथ ही, रॉ बाइट ऐरे को सीधे तौर पर पास किया जा सकता है:

// Loading a custom WASM matcher
val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() }
val customRequest = RegisterExportRequest(
    entries = myEntries,
    exportMatcher = customMatcherBytes
)
providerEventsManager.registerExport(customRequest)