ওয়েবভিউ ব্যবহার করে এমন একটি অ্যান্ড্রয়েড অ্যাপের সাথে ক্রেডেনশিয়াল ম্যানেজার এপিআই কীভাবে একীভূত করা যায় তা এই দস্তাবেজটি বর্ণনা করে।
ওভারভিউ
ইন্টিগ্রেশন প্রক্রিয়ায় ডুব দেওয়ার আগে, নেটিভ অ্যান্ড্রয়েড কোড, একটি ওয়েব কম্পোনেন্ট যা আপনার অ্যাপের প্রমাণীকরণ পরিচালনা করে এবং একটি ব্যাকএন্ডের মধ্যে রেন্ডার করা একটি ওয়েব উপাদানের মধ্যে যোগাযোগের প্রবাহ বোঝা গুরুত্বপূর্ণ। প্রবাহে নিবন্ধন (প্রমাণপত্র তৈরি করা) এবং প্রমাণীকরণ (বিদ্যমান শংসাপত্রগুলি প্রাপ্ত করা) জড়িত।
নিবন্ধন (একটি পাসকি তৈরি করুন)
- ব্যাকএন্ড প্রাথমিক রেজিস্ট্রেশন JSON তৈরি করে এবং WebView-এর মধ্যে রেন্ডার করা ওয়েব পেজে পাঠায়।
- ওয়েব পৃষ্ঠাটি নতুন শংসাপত্র নিবন্ধন করতে
navigator.credentials.create()
ব্যবহার করে। আপনি Android অ্যাপে অনুরোধ পাঠানোর জন্য পরবর্তী ধাপে এই পদ্ধতিটিকে ওভাররাইড করতে ইনজেকশন করা জাভাস্ক্রিপ্ট ব্যবহার করবেন। - অ্যান্ড্রয়েড অ্যাপটি ক্রেডেনশিয়াল ম্যানেজার এপিআই ব্যবহার করে শংসাপত্রের অনুরোধ তৈরি করতে এবং
createCredential
করতে ব্যবহার করে। - ক্রেডেনশিয়াল ম্যানেজার API অ্যাপটির সাথে পাবলিক কী শংসাপত্র শেয়ার করে।
- অ্যাপটি পাবলিক কী শংসাপত্রকে ওয়েব পৃষ্ঠায় ফেরত পাঠায় যাতে ইনজেকশন করা জাভাস্ক্রিপ্ট প্রতিক্রিয়াগুলিকে পার্স করতে পারে।
- ওয়েব পৃষ্ঠাটি সর্বজনীন কীটিকে ব্যাকএন্ডে পাঠায়, যা সর্বজনীন কী যাচাই করে এবং সংরক্ষণ করে।
প্রমাণীকরণ (একটি পাসকি পান)
- শংসাপত্র পাওয়ার জন্য ব্যাকএন্ড প্রমাণীকরণ JSON জেনারেট করে এবং ওয়েব ভিউ ক্লায়েন্টে রেন্ডার করা ওয়েব পৃষ্ঠায় এটি পাঠায়।
- ওয়েব পেজ
navigator.credentials.get
ব্যবহার করে। অনুরোধটি অ্যান্ড্রয়েড অ্যাপে পুনঃনির্দেশ করতে এই পদ্ধতিটি ওভাররাইড করতে ইনজেকশন করা জাভাস্ক্রিপ্ট ব্যবহার করুন। - অ্যাপটি
getCredential
কল করে ক্রেডেনশিয়াল ম্যানেজার API ব্যবহার করে শংসাপত্র পুনরুদ্ধার করে। - ক্রেডেনশিয়াল ম্যানেজার API অ্যাপে শংসাপত্র ফেরত দেয়।
- অ্যাপটি প্রাইভেট কী-এর ডিজিটাল স্বাক্ষর পায় এবং এটি ওয়েব পৃষ্ঠায় পাঠায় যাতে ইনজেকশন করা জাভাস্ক্রিপ্ট প্রতিক্রিয়াগুলি বিশ্লেষণ করতে পারে।
- তারপর ওয়েব পৃষ্ঠাটি সার্ভারে পাঠায় যা পাবলিক কী দিয়ে ডিজিটাল স্বাক্ষর যাচাই করে।
একই প্রবাহ পাসওয়ার্ড বা ফেডারেটেড আইডেন্টিটি সিস্টেমের জন্য ব্যবহার করা যেতে পারে।
পূর্বশর্ত
ক্রেডেনশিয়াল ম্যানেজার এপিআই ব্যবহার করতে, ক্রেডেনশিয়াল ম্যানেজার গাইডের পূর্বশর্ত বিভাগে বর্ণিত পদক্ষেপগুলি সম্পূর্ণ করুন এবং নিশ্চিত করুন যে আপনি নিম্নলিখিতগুলি করেছেন:
- প্রয়োজনীয় নির্ভরতা যোগ করুন ।
- ProGuard ফাইলে ক্লাস সংরক্ষণ করুন ।
- ডিজিটাল সম্পদ লিঙ্কের জন্য সমর্থন যোগ করুন ।
জাভাস্ক্রিপ্ট যোগাযোগ
ওয়েবভিউতে জাভাস্ক্রিপ্ট এবং নেটিভ অ্যান্ড্রয়েড কোডকে একে অপরের সাথে কথা বলার অনুমতি দিতে, আপনাকে দুটি পরিবেশের মধ্যে বার্তা পাঠাতে এবং অনুরোধগুলি পরিচালনা করতে হবে। এটি করার জন্য, একটি ওয়েবভিউতে কাস্টম জাভাস্ক্রিপ্ট কোড ইনজেক্ট করুন। এটি আপনাকে ওয়েব সামগ্রীর আচরণ পরিবর্তন করতে এবং নেটিভ অ্যান্ড্রয়েড কোডের সাথে ইন্টারঅ্যাক্ট করতে দেয়৷
জাভাস্ক্রিপ্ট ইনজেকশন
নিম্নলিখিত জাভাস্ক্রিপ্ট কোড WebView এবং Android অ্যাপের মধ্যে যোগাযোগ স্থাপন করে। এটি navigator.credentials.create()
এবং navigator.credentials.get()
পদ্ধতিগুলিকে ওভাররাইড করে যা আগে বর্ণিত নিবন্ধকরণ এবং প্রমাণীকরণ প্রবাহের জন্য WebAuthn API দ্বারা ব্যবহৃত হয়৷
আপনার অ্যাপ্লিকেশনে এই জাভাস্ক্রিপ্ট কোডের সংক্ষিপ্ত সংস্করণ ব্যবহার করুন।
পাসকিগুলির জন্য একটি শ্রোতা তৈরি করুন
একটি PasskeyWebListener
ক্লাস সেট আপ করুন যা JavaScript এর সাথে যোগাযোগ পরিচালনা করে। এই ক্লাসটি WebViewCompat.WebMessageListener
থেকে উত্তরাধিকারসূত্রে পাওয়া উচিত। এই ক্লাসটি জাভাস্ক্রিপ্ট থেকে বার্তা পায় এবং অ্যান্ড্রয়েড অ্যাপে প্রয়োজনীয় ক্রিয়া সম্পাদন করে।
কোটলিন
// The class talking to Javascript should inherit:
class PasskeyWebListener(
private val activity: Activity,
private val coroutineScope: CoroutineScope,
private val credentialManagerHandler: CredentialManagerHandler
) : WebViewCompat.WebMessageListener
// ... Implementation details
জাভা
// The class talking to Javascript should inherit:
class PasskeyWebListener implements WebViewCompat.WebMessageListener {
// Implementation details
private Activity activity;
// Handles get/create methods meant for Java:
private CredentialManagerHandler credentialManagerHandler;
public PasskeyWebListener(
Activity activity,
CredentialManagerHandler credentialManagerHandler
) {
this.activity = activity;
this.credentialManagerHandler = credentialManagerHandler;
}
// ... Implementation details
}
PasskeyWebListener
ভিতরে, নিম্নলিখিত বিভাগে বর্ণিত অনুরোধ এবং প্রতিক্রিয়াগুলির জন্য যুক্তি প্রয়োগ করুন।
প্রমাণীকরণ অনুরোধ হ্যান্ডেল
WebAuthn navigator.credentials.create()
বা navigator.credentials.get()
অপারেশনগুলির জন্য অনুরোধগুলি পরিচালনা করতে, যখন JavaScript কোড Android অ্যাপে একটি বার্তা পাঠায় তখন PasskeyWebListener
ক্লাসের onPostMessage
পদ্ধতিটি কল করা হয়:
কোটলিন
class PasskeyWebListener(...)... {
// ...
/** havePendingRequest is true if there is an outstanding WebAuthn request.
There is only ever one request outstanding at a time. */
private var havePendingRequest = false
/** pendingRequestIsDoomed is true if the WebView has navigated since
starting a request. The FIDO module cannot be canceled, but the response
will never be delivered in this case. */
private var pendingRequestIsDoomed = false
/** replyChannel is the port that the page is listening for a response on.
It is valid if havePendingRequest is true. */
private var replyChannel: ReplyChannel? = null
/**
* Called by the page during a WebAuthn request.
*
* @param view Creates the WebView.
* @param message The message sent from the client using injected JavaScript.
* @param sourceOrigin The origin of the HTTPS request. Should not be null.
* @param isMainFrame Should be set to true. Embedded frames are not
supported.
* @param replyProxy Passed in by JavaScript. Allows replying when wrapped in
the Channel.
* @return The message response.
*/
@UiThread
override fun onPostMessage(
view: WebView,
message: WebMessageCompat,
sourceOrigin: Uri,
isMainFrame: Boolean,
replyProxy: JavaScriptReplyProxy,
) {
val messageData = message.data ?: return
onRequest(
messageData,
sourceOrigin,
isMainFrame,
JavaScriptReplyChannel(replyProxy)
)
}
private fun onRequest(
msg: String,
sourceOrigin: Uri,
isMainFrame: Boolean,
reply: ReplyChannel,
) {
msg?.let {
val jsonObj = JSONObject(msg);
val type = jsonObj.getString(TYPE_KEY)
val message = jsonObj.getString(REQUEST_KEY)
if (havePendingRequest) {
postErrorMessage(reply, "The request already in progress", type)
return
}
replyChannel = reply
if (!isMainFrame) {
reportFailure("Requests from subframes are not supported", type)
return
}
val originScheme = sourceOrigin.scheme
if (originScheme == null || originScheme.lowercase() != "https") {
reportFailure("WebAuthn not permitted for current URL", type)
return
}
// Verify that origin belongs to your website,
// it's because the unknown origin may gain credential info.
if (isUnknownOrigin(originScheme)) {
return
}
havePendingRequest = true
pendingRequestIsDoomed = false
// Use a temporary "replyCurrent" variable to send the data back, while
// resetting the main "replyChannel" variable to null so it’s ready for
// the next request.
val replyCurrent = replyChannel
if (replyCurrent == null) {
Log.i(TAG, "The reply channel was null, cannot continue")
return;
}
when (type) {
CREATE_UNIQUE_KEY ->
this.coroutineScope.launch {
handleCreateFlow(credentialManagerHandler, message, replyCurrent)
}
GET_UNIQUE_KEY -> this.coroutineScope.launch {
handleGetFlow(credentialManagerHandler, message, replyCurrent)
}
else -> Log.i(TAG, "Incorrect request json")
}
}
}
private suspend fun handleCreateFlow(
credentialManagerHandler: CredentialManagerHandler,
message: String,
reply: ReplyChannel,
) {
try {
havePendingRequest = false
pendingRequestIsDoomed = false
val response = credentialManagerHandler.createPasskey(message)
val successArray = ArrayList<Any>();
successArray.add("success");
successArray.add(JSONObject(response.registrationResponseJson));
successArray.add(CREATE_UNIQUE_KEY);
reply.send(JSONArray(successArray).toString())
replyChannel = null // setting initial replyChannel for the next request
} catch (e: CreateCredentialException) {
reportFailure(
"Error: ${e.errorMessage} w type: ${e.type} w obj: $e",
CREATE_UNIQUE_KEY
)
} catch (t: Throwable) {
reportFailure("Error: ${t.message}", CREATE_UNIQUE_KEY)
}
}
companion object {
const val TYPE_KEY = "type"
const val REQUEST_KEY = "request"
const val CREATE_UNIQUE_KEY = "create"
const val GET_UNIQUE_KEY = "get"
}
}
জাভা
class PasskeyWebListener implements ... {
// ...
/**
* Called by the page during a WebAuthn request.
*
* @param view Creates the WebView.
* @param message The message sent from the client using injected JavaScript.
* @param sourceOrigin The origin of the HTTPS request. Should not be null.
* @param isMainFrame Should be set to true. Embedded frames are not
supported.
* @param replyProxy Passed in by JavaScript. Allows replying when wrapped in
the Channel.
* @return The message response.
*/
@UiThread
public void onPostMessage(
@NonNull WebView view,
@NonNull WebMessageCompat message,
@NonNull Uri sourceOrigin,
Boolean isMainFrame,
@NonNull JavaScriptReplyProxy replyProxy,
) {
if (messageData == null) {
return;
}
onRequest(
messageData,
sourceOrigin,
isMainFrame,
JavaScriptReplyChannel(replyProxy)
)
}
private void onRequest(
String msg,
Uri sourceOrigin,
boolean isMainFrame,
ReplyChannel reply
) {
if (msg != null) {
try {
JSONObject jsonObj = new JSONObject(msg);
String type = jsonObj.getString(TYPE_KEY);
String message = jsonObj.getString(REQUEST_KEY);
boolean isCreate = type.equals(CREATE_UNIQUE_KEY);
boolean isGet = type.equals(GET_UNIQUE_KEY);
if (havePendingRequest) {
postErrorMessage(reply, "The request already in progress", type);
return;
}
replyChannel = reply;
if (!isMainFrame) {
reportFailure("Requests from subframes are not supported", type);
return;
}
String originScheme = sourceOrigin.getScheme();
if (originScheme == null || !originScheme.toLowerCase().equals("https")) {
reportFailure("WebAuthn not permitted for current URL", type);
return;
}
// Verify that origin belongs to your website,
// Requests of unknown origin may gain access to credential info.
if (isUnknownOrigin(originScheme)) {
return;
}
havePendingRequest = true;
pendingRequestIsDoomed = false;
// Use a temporary "replyCurrent" variable to send the data back,
// while resetting the main "replyChannel" variable to null so it’s
// ready for the next request.
ReplyChannel replyCurrent = replyChannel;
if (replyCurrent == null) {
Log.i(TAG, "The reply channel was null, cannot continue");
return;
}
if (isCreate) {
handleCreateFlow(credentialManagerHandler, message, replyCurrent));
} else if (isGet) {
handleGetFlow(credentialManagerHandler, message, replyCurrent));
} else {
Log.i(TAG, "Incorrect request json");
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
handleCreateFlow
এবং handleGetFlow
এর জন্য, GitHub-এর উদাহরণ পড়ুন।
প্রতিক্রিয়া হ্যান্ডেল
নেটিভ অ্যাপ থেকে ওয়েব পেজে পাঠানো প্রতিক্রিয়াগুলি পরিচালনা করতে, JavaScriptReplyChannel
মধ্যে JavaScriptReplyProxy
যোগ করুন।
কোটলিন
class PasskeyWebListener(...)... {
// ...
// The setup for the reply channel allows communication with JavaScript.
private class JavaScriptReplyChannel(private val reply: JavaScriptReplyProxy) :
ReplyChannel {
override fun send(message: String?) {
try {
reply.postMessage(message!!)
} catch (t: Throwable) {
Log.i(TAG, "Reply failure due to: " + t.message);
}
}
}
// ReplyChannel is the interface where replies to the embedded site are
// sent. This allows for testing since AndroidX bans mocking its objects.
interface ReplyChannel {
fun send(message: String?)
}
}
জাভা
class PasskeyWebListener implements ... {
// ...
// The setup for the reply channel allows communication with JavaScript.
private static class JavaScriptReplyChannel implements ReplyChannel {
private final JavaScriptReplyProxy reply;
JavaScriptReplyChannel(JavaScriptReplyProxy reply) {
this.reply = reply;
}
@Override
public void send(String message) {
reply.postMessage(message);
}
}
// ReplyChannel is the interface where replies to the embedded site are
// sent. This allows for testing since AndroidX bans mocking its objects.
interface ReplyChannel {
void send(String message);
}
}
নেটিভ অ্যাপ থেকে কোনো ত্রুটি ধরা এবং জাভাস্ক্রিপ্ট সাইডে তাদের ফেরত পাঠাতে ভুলবেন না।
WebView এর সাথে ইন্টিগ্রেট করুন
এই বিভাগটি বর্ণনা করে কিভাবে আপনার WebView ইন্টিগ্রেশন সেট আপ করবেন।
ওয়েবভিউ আরম্ভ করুন
আপনার অ্যান্ড্রয়েড অ্যাপের কার্যকলাপে, একটি WebView
শুরু করুন এবং একটি সহগামী WebViewClient
সেট আপ করুন। WebViewClient
WebView
এ ইনজেক্ট করা JavaScript কোডের সাথে যোগাযোগ পরিচালনা করে।
WebView সেট আপ করুন এবং ক্রেডেনশিয়াল ম্যানেজারকে কল করুন:
কোটলিন
val credentialManagerHandler = CredentialManagerHandler(this)
// ...
AndroidView(factory = {
WebView(it).apply {
settings.javaScriptEnabled = true
// Test URL:
val url = "https://credman-web-test.glitch.me/"
val listenerSupported = WebViewFeature.isFeatureSupported(
WebViewFeature.WEB_MESSAGE_LISTENER
)
if (listenerSupported) {
// Inject local JavaScript that calls Credential Manager.
hookWebAuthnWithListener(this, this@MainActivity,
coroutineScope, credentialManagerHandler)
} else {
// Fallback routine for unsupported API levels.
}
loadUrl(url)
}
}
)
জাভা
// Example shown in the onCreate method of an Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = findViewById(R.id.web_view);
// Test URL:
String url = "https://credman-web-test.glitch.me/";
Boolean listenerSupported = WebViewFeature.isFeatureSupported(
WebViewFeature.WEB_MESSAGE_LISTENER
);
if (listenerSupported) {
// Inject local JavaScript that calls Credential Manager.
hookWebAuthnWithListener(webView, this,
coroutineScope, credentialManagerHandler)
} else {
// Fallback routine for unsupported API levels.
}
webView.loadUrl(url);
}
একটি নতুন WebView ক্লায়েন্ট অবজেক্ট তৈরি করুন এবং ওয়েব পৃষ্ঠায় জাভাস্ক্রিপ্ট ইনজেক্ট করুন:
কোটলিন
// This is an example call into hookWebAuthnWithListener
val passkeyWebListener = PasskeyWebListener(
activity, coroutineScope, credentialManagerHandler
)
val webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
// Handle page load events
passkeyWebListener.onPageStarted();
webView.evaluateJavascript(PasskeyWebListener.INJECTED_VAL, null)
}
}
webView.webViewClient = webViewClient
জাভা
// This is an example call into hookWebAuthnWithListener
PasskeyWebListener passkeyWebListener = new PasskeyWebListener(
activity, credentialManagerHandler
)
WebViewClient webiewClient = new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
// Handle page load events
passkeyWebListener.onPageStarted();
webView.evaulateJavascript(PasskeyWebListener.INJECTED_VAL, null);
}
};
webView.setWebViewClient(webViewClient);
একটি ওয়েব বার্তা শ্রোতা সেট আপ করুন
JavaScript এবং Android অ্যাপের মধ্যে বার্তাগুলি পোস্ট করার অনুমতি দিতে, WebViewCompat.addWebMessageListener
পদ্ধতির সাথে একটি ওয়েব বার্তা শ্রোতা সেট আপ করুন৷
কোটলিন
val rules = setOf("*")
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
WebViewCompat.addWebMessageListener(
webView, PasskeyWebListener.INTERFACE_NAME, rules, passkeyWebListener
)
}
জাভা
Set<String> rules = new HashSet<>(Arrays.asList("*"));
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
WebViewCompat.addWebMessageListener(
webView, PasskeyWebListener.INTERFACE_NAME, rules, passkeyWebListener
)
}
ওয়েব ইন্টিগ্রেশন
কিভাবে ওয়েব ইন্টিগ্রেশন চেকআউট তৈরি করতে হয় তা শিখতে পাসওয়ার্ডহীন লগইনের জন্য একটি পাসকি তৈরি করুন এবং ফর্ম অটোফিলের মাধ্যমে একটি পাসকি দিয়ে সাইন ইন করুন ।
পরীক্ষা এবং স্থাপনা
অ্যান্ড্রয়েড অ্যাপ, ওয়েব পৃষ্ঠা এবং ব্যাকএন্ডের মধ্যে সঠিক যোগাযোগ নিশ্চিত করতে একটি নিয়ন্ত্রিত পরিবেশে সম্পূর্ণ প্রবাহটি পুঙ্খানুপুঙ্খভাবে পরীক্ষা করুন।
উৎপাদনে সমন্বিত সমাধান স্থাপন করুন, নিশ্চিত করুন যে ব্যাকএন্ড ইনকামিং রেজিস্ট্রেশন এবং প্রমাণীকরণ অনুরোধগুলি পরিচালনা করতে পারে। ব্যাকএন্ড কোডটি রেজিস্ট্রেশন (তৈরি করা) এবং প্রমাণীকরণ (পাওয়া) প্রক্রিয়াগুলির জন্য প্রাথমিক JSON তৈরি করা উচিত। এটি ওয়েব পৃষ্ঠা থেকে প্রাপ্ত প্রতিক্রিয়াগুলির বৈধতা এবং যাচাইকরণও পরিচালনা করবে।
যাচাই করুন বাস্তবায়ন UX সুপারিশের সাথে মিলে যায়।
গুরুত্বপূর্ণ নোট
-
navigator.credentials.create()
এবংnavigator.credentials.get()
অপারেশন পরিচালনা করতে প্রদত্ত জাভাস্ক্রিপ্ট কোড ব্যবহার করুন। -
PasskeyWebListener
ক্লাস হল Android অ্যাপ এবং WebView-এ JavaScript কোডের মধ্যে সেতু। এটি বার্তা প্রেরণ, যোগাযোগ এবং প্রয়োজনীয় ক্রিয়া সম্পাদন পরিচালনা করে। - প্রদত্ত কোড স্নিপেটগুলিকে আপনার প্রকল্পের কাঠামো, নামকরণের নিয়মাবলী এবং আপনার যে কোনো নির্দিষ্ট প্রয়োজনীয়তার সাথে মানানসই করে নিন।
- নেটিভ অ্যাপের দিক থেকে ত্রুটিগুলি ধরুন এবং সেগুলিকে জাভাস্ক্রিপ্টের দিকে ফেরত পাঠান৷
এই নির্দেশিকা অনুসরণ করে এবং WebView ব্যবহার করে এমন আপনার Android অ্যাপে ক্রেডেনশিয়াল ম্যানেজার API-কে একীভূত করার মাধ্যমে, আপনি আপনার ব্যবহারকারীদের তাদের শংসাপত্রগুলি কার্যকরভাবে পরিচালনা করার সময় তাদের জন্য একটি নিরাপদ এবং বিরামহীন পাসকি-সক্ষম লগইন অভিজ্ঞতা প্রদান করতে পারেন।
,ওয়েবভিউ ব্যবহার করে এমন একটি অ্যান্ড্রয়েড অ্যাপের সাথে ক্রেডেনশিয়াল ম্যানেজার এপিআই কীভাবে একীভূত করা যায় তা এই দস্তাবেজটি বর্ণনা করে।
ওভারভিউ
ইন্টিগ্রেশন প্রক্রিয়ায় ডুব দেওয়ার আগে, নেটিভ অ্যান্ড্রয়েড কোড, একটি ওয়েব কম্পোনেন্ট যা আপনার অ্যাপের প্রমাণীকরণ পরিচালনা করে এবং একটি ব্যাকএন্ডের মধ্যে রেন্ডার করা একটি ওয়েব উপাদানের মধ্যে যোগাযোগের প্রবাহ বোঝা গুরুত্বপূর্ণ। প্রবাহে নিবন্ধন (প্রমাণপত্র তৈরি করা) এবং প্রমাণীকরণ (বিদ্যমান শংসাপত্রগুলি প্রাপ্ত করা) জড়িত।
নিবন্ধন (একটি পাসকি তৈরি করুন)
- ব্যাকএন্ড প্রাথমিক রেজিস্ট্রেশন JSON তৈরি করে এবং WebView-এর মধ্যে রেন্ডার করা ওয়েব পেজে পাঠায়।
- ওয়েব পৃষ্ঠাটি নতুন শংসাপত্র নিবন্ধন করতে
navigator.credentials.create()
ব্যবহার করে। আপনি Android অ্যাপে অনুরোধ পাঠানোর জন্য পরবর্তী ধাপে এই পদ্ধতিটিকে ওভাররাইড করতে ইনজেকশন করা জাভাস্ক্রিপ্ট ব্যবহার করবেন। - অ্যান্ড্রয়েড অ্যাপটি ক্রেডেনশিয়াল ম্যানেজার এপিআই ব্যবহার করে শংসাপত্রের অনুরোধ তৈরি করতে এবং
createCredential
করতে ব্যবহার করে। - ক্রেডেনশিয়াল ম্যানেজার API অ্যাপটির সাথে পাবলিক কী শংসাপত্র শেয়ার করে।
- অ্যাপটি পাবলিক কী শংসাপত্রকে ওয়েব পৃষ্ঠায় ফেরত পাঠায় যাতে ইনজেকশন করা জাভাস্ক্রিপ্ট প্রতিক্রিয়াগুলিকে পার্স করতে পারে।
- ওয়েব পৃষ্ঠাটি সর্বজনীন কীটিকে ব্যাকএন্ডে পাঠায়, যা সর্বজনীন কী যাচাই করে এবং সংরক্ষণ করে।
প্রমাণীকরণ (একটি পাসকি পান)
- শংসাপত্র পাওয়ার জন্য ব্যাকএন্ড প্রমাণীকরণ JSON জেনারেট করে এবং ওয়েব ভিউ ক্লায়েন্টে রেন্ডার করা ওয়েব পৃষ্ঠায় এটি পাঠায়।
- ওয়েব পেজ
navigator.credentials.get
ব্যবহার করে। অনুরোধটি অ্যান্ড্রয়েড অ্যাপে পুনঃনির্দেশ করতে এই পদ্ধতিটি ওভাররাইড করতে ইনজেকশন করা জাভাস্ক্রিপ্ট ব্যবহার করুন। - অ্যাপটি
getCredential
কল করে ক্রেডেনশিয়াল ম্যানেজার API ব্যবহার করে শংসাপত্র পুনরুদ্ধার করে। - ক্রেডেনশিয়াল ম্যানেজার API অ্যাপে শংসাপত্র ফেরত দেয়।
- অ্যাপটি প্রাইভেট কী-এর ডিজিটাল স্বাক্ষর পায় এবং এটি ওয়েব পৃষ্ঠায় পাঠায় যাতে ইনজেকশন করা জাভাস্ক্রিপ্ট প্রতিক্রিয়াগুলি বিশ্লেষণ করতে পারে।
- তারপর ওয়েব পৃষ্ঠাটি সার্ভারে পাঠায় যা পাবলিক কী দিয়ে ডিজিটাল স্বাক্ষর যাচাই করে।
একই প্রবাহ পাসওয়ার্ড বা ফেডারেটেড আইডেন্টিটি সিস্টেমের জন্য ব্যবহার করা যেতে পারে।
পূর্বশর্ত
ক্রেডেনশিয়াল ম্যানেজার এপিআই ব্যবহার করতে, ক্রেডেনশিয়াল ম্যানেজার গাইডের পূর্বশর্ত বিভাগে বর্ণিত পদক্ষেপগুলি সম্পূর্ণ করুন এবং নিশ্চিত করুন যে আপনি নিম্নলিখিতগুলি করেছেন:
- প্রয়োজনীয় নির্ভরতা যোগ করুন ।
- ProGuard ফাইলে ক্লাস সংরক্ষণ করুন ।
- ডিজিটাল সম্পদ লিঙ্কের জন্য সমর্থন যোগ করুন ।
জাভাস্ক্রিপ্ট যোগাযোগ
ওয়েবভিউতে জাভাস্ক্রিপ্ট এবং নেটিভ অ্যান্ড্রয়েড কোডকে একে অপরের সাথে কথা বলার অনুমতি দিতে, আপনাকে দুটি পরিবেশের মধ্যে বার্তা পাঠাতে এবং অনুরোধগুলি পরিচালনা করতে হবে। এটি করার জন্য, একটি ওয়েবভিউতে কাস্টম জাভাস্ক্রিপ্ট কোড ইনজেক্ট করুন। এটি আপনাকে ওয়েব সামগ্রীর আচরণ পরিবর্তন করতে এবং নেটিভ অ্যান্ড্রয়েড কোডের সাথে ইন্টারঅ্যাক্ট করতে দেয়৷
জাভাস্ক্রিপ্ট ইনজেকশন
নিম্নলিখিত জাভাস্ক্রিপ্ট কোড WebView এবং Android অ্যাপের মধ্যে যোগাযোগ স্থাপন করে। এটি navigator.credentials.create()
এবং navigator.credentials.get()
পদ্ধতিগুলিকে ওভাররাইড করে যা আগে বর্ণিত নিবন্ধকরণ এবং প্রমাণীকরণ প্রবাহের জন্য WebAuthn API দ্বারা ব্যবহৃত হয়৷
আপনার অ্যাপ্লিকেশনে এই জাভাস্ক্রিপ্ট কোডের সংক্ষিপ্ত সংস্করণ ব্যবহার করুন।
পাসকিগুলির জন্য একটি শ্রোতা তৈরি করুন
একটি PasskeyWebListener
ক্লাস সেট আপ করুন যা JavaScript এর সাথে যোগাযোগ পরিচালনা করে। এই ক্লাসটি WebViewCompat.WebMessageListener
থেকে উত্তরাধিকারসূত্রে পাওয়া উচিত। এই ক্লাসটি জাভাস্ক্রিপ্ট থেকে বার্তা পায় এবং অ্যান্ড্রয়েড অ্যাপে প্রয়োজনীয় ক্রিয়া সম্পাদন করে।
কোটলিন
// The class talking to Javascript should inherit:
class PasskeyWebListener(
private val activity: Activity,
private val coroutineScope: CoroutineScope,
private val credentialManagerHandler: CredentialManagerHandler
) : WebViewCompat.WebMessageListener
// ... Implementation details
জাভা
// The class talking to Javascript should inherit:
class PasskeyWebListener implements WebViewCompat.WebMessageListener {
// Implementation details
private Activity activity;
// Handles get/create methods meant for Java:
private CredentialManagerHandler credentialManagerHandler;
public PasskeyWebListener(
Activity activity,
CredentialManagerHandler credentialManagerHandler
) {
this.activity = activity;
this.credentialManagerHandler = credentialManagerHandler;
}
// ... Implementation details
}
PasskeyWebListener
ভিতরে, নিম্নলিখিত বিভাগে বর্ণিত অনুরোধ এবং প্রতিক্রিয়াগুলির জন্য যুক্তি প্রয়োগ করুন।
প্রমাণীকরণ অনুরোধ হ্যান্ডেল
WebAuthn navigator.credentials.create()
বা navigator.credentials.get()
অপারেশনগুলির জন্য অনুরোধগুলি পরিচালনা করতে, যখন JavaScript কোড Android অ্যাপে একটি বার্তা পাঠায় তখন PasskeyWebListener
ক্লাসের onPostMessage
পদ্ধতিটি কল করা হয়:
কোটলিন
class PasskeyWebListener(...)... {
// ...
/** havePendingRequest is true if there is an outstanding WebAuthn request.
There is only ever one request outstanding at a time. */
private var havePendingRequest = false
/** pendingRequestIsDoomed is true if the WebView has navigated since
starting a request. The FIDO module cannot be canceled, but the response
will never be delivered in this case. */
private var pendingRequestIsDoomed = false
/** replyChannel is the port that the page is listening for a response on.
It is valid if havePendingRequest is true. */
private var replyChannel: ReplyChannel? = null
/**
* Called by the page during a WebAuthn request.
*
* @param view Creates the WebView.
* @param message The message sent from the client using injected JavaScript.
* @param sourceOrigin The origin of the HTTPS request. Should not be null.
* @param isMainFrame Should be set to true. Embedded frames are not
supported.
* @param replyProxy Passed in by JavaScript. Allows replying when wrapped in
the Channel.
* @return The message response.
*/
@UiThread
override fun onPostMessage(
view: WebView,
message: WebMessageCompat,
sourceOrigin: Uri,
isMainFrame: Boolean,
replyProxy: JavaScriptReplyProxy,
) {
val messageData = message.data ?: return
onRequest(
messageData,
sourceOrigin,
isMainFrame,
JavaScriptReplyChannel(replyProxy)
)
}
private fun onRequest(
msg: String,
sourceOrigin: Uri,
isMainFrame: Boolean,
reply: ReplyChannel,
) {
msg?.let {
val jsonObj = JSONObject(msg);
val type = jsonObj.getString(TYPE_KEY)
val message = jsonObj.getString(REQUEST_KEY)
if (havePendingRequest) {
postErrorMessage(reply, "The request already in progress", type)
return
}
replyChannel = reply
if (!isMainFrame) {
reportFailure("Requests from subframes are not supported", type)
return
}
val originScheme = sourceOrigin.scheme
if (originScheme == null || originScheme.lowercase() != "https") {
reportFailure("WebAuthn not permitted for current URL", type)
return
}
// Verify that origin belongs to your website,
// it's because the unknown origin may gain credential info.
if (isUnknownOrigin(originScheme)) {
return
}
havePendingRequest = true
pendingRequestIsDoomed = false
// Use a temporary "replyCurrent" variable to send the data back, while
// resetting the main "replyChannel" variable to null so it’s ready for
// the next request.
val replyCurrent = replyChannel
if (replyCurrent == null) {
Log.i(TAG, "The reply channel was null, cannot continue")
return;
}
when (type) {
CREATE_UNIQUE_KEY ->
this.coroutineScope.launch {
handleCreateFlow(credentialManagerHandler, message, replyCurrent)
}
GET_UNIQUE_KEY -> this.coroutineScope.launch {
handleGetFlow(credentialManagerHandler, message, replyCurrent)
}
else -> Log.i(TAG, "Incorrect request json")
}
}
}
private suspend fun handleCreateFlow(
credentialManagerHandler: CredentialManagerHandler,
message: String,
reply: ReplyChannel,
) {
try {
havePendingRequest = false
pendingRequestIsDoomed = false
val response = credentialManagerHandler.createPasskey(message)
val successArray = ArrayList<Any>();
successArray.add("success");
successArray.add(JSONObject(response.registrationResponseJson));
successArray.add(CREATE_UNIQUE_KEY);
reply.send(JSONArray(successArray).toString())
replyChannel = null // setting initial replyChannel for the next request
} catch (e: CreateCredentialException) {
reportFailure(
"Error: ${e.errorMessage} w type: ${e.type} w obj: $e",
CREATE_UNIQUE_KEY
)
} catch (t: Throwable) {
reportFailure("Error: ${t.message}", CREATE_UNIQUE_KEY)
}
}
companion object {
const val TYPE_KEY = "type"
const val REQUEST_KEY = "request"
const val CREATE_UNIQUE_KEY = "create"
const val GET_UNIQUE_KEY = "get"
}
}
জাভা
class PasskeyWebListener implements ... {
// ...
/**
* Called by the page during a WebAuthn request.
*
* @param view Creates the WebView.
* @param message The message sent from the client using injected JavaScript.
* @param sourceOrigin The origin of the HTTPS request. Should not be null.
* @param isMainFrame Should be set to true. Embedded frames are not
supported.
* @param replyProxy Passed in by JavaScript. Allows replying when wrapped in
the Channel.
* @return The message response.
*/
@UiThread
public void onPostMessage(
@NonNull WebView view,
@NonNull WebMessageCompat message,
@NonNull Uri sourceOrigin,
Boolean isMainFrame,
@NonNull JavaScriptReplyProxy replyProxy,
) {
if (messageData == null) {
return;
}
onRequest(
messageData,
sourceOrigin,
isMainFrame,
JavaScriptReplyChannel(replyProxy)
)
}
private void onRequest(
String msg,
Uri sourceOrigin,
boolean isMainFrame,
ReplyChannel reply
) {
if (msg != null) {
try {
JSONObject jsonObj = new JSONObject(msg);
String type = jsonObj.getString(TYPE_KEY);
String message = jsonObj.getString(REQUEST_KEY);
boolean isCreate = type.equals(CREATE_UNIQUE_KEY);
boolean isGet = type.equals(GET_UNIQUE_KEY);
if (havePendingRequest) {
postErrorMessage(reply, "The request already in progress", type);
return;
}
replyChannel = reply;
if (!isMainFrame) {
reportFailure("Requests from subframes are not supported", type);
return;
}
String originScheme = sourceOrigin.getScheme();
if (originScheme == null || !originScheme.toLowerCase().equals("https")) {
reportFailure("WebAuthn not permitted for current URL", type);
return;
}
// Verify that origin belongs to your website,
// Requests of unknown origin may gain access to credential info.
if (isUnknownOrigin(originScheme)) {
return;
}
havePendingRequest = true;
pendingRequestIsDoomed = false;
// Use a temporary "replyCurrent" variable to send the data back,
// while resetting the main "replyChannel" variable to null so it’s
// ready for the next request.
ReplyChannel replyCurrent = replyChannel;
if (replyCurrent == null) {
Log.i(TAG, "The reply channel was null, cannot continue");
return;
}
if (isCreate) {
handleCreateFlow(credentialManagerHandler, message, replyCurrent));
} else if (isGet) {
handleGetFlow(credentialManagerHandler, message, replyCurrent));
} else {
Log.i(TAG, "Incorrect request json");
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
handleCreateFlow
এবং handleGetFlow
এর জন্য, GitHub-এর উদাহরণ পড়ুন।
প্রতিক্রিয়া হ্যান্ডেল
নেটিভ অ্যাপ থেকে ওয়েব পেজে পাঠানো প্রতিক্রিয়াগুলি পরিচালনা করতে, JavaScriptReplyChannel
মধ্যে JavaScriptReplyProxy
যোগ করুন।
কোটলিন
class PasskeyWebListener(...)... {
// ...
// The setup for the reply channel allows communication with JavaScript.
private class JavaScriptReplyChannel(private val reply: JavaScriptReplyProxy) :
ReplyChannel {
override fun send(message: String?) {
try {
reply.postMessage(message!!)
} catch (t: Throwable) {
Log.i(TAG, "Reply failure due to: " + t.message);
}
}
}
// ReplyChannel is the interface where replies to the embedded site are
// sent. This allows for testing since AndroidX bans mocking its objects.
interface ReplyChannel {
fun send(message: String?)
}
}
জাভা
class PasskeyWebListener implements ... {
// ...
// The setup for the reply channel allows communication with JavaScript.
private static class JavaScriptReplyChannel implements ReplyChannel {
private final JavaScriptReplyProxy reply;
JavaScriptReplyChannel(JavaScriptReplyProxy reply) {
this.reply = reply;
}
@Override
public void send(String message) {
reply.postMessage(message);
}
}
// ReplyChannel is the interface where replies to the embedded site are
// sent. This allows for testing since AndroidX bans mocking its objects.
interface ReplyChannel {
void send(String message);
}
}
নেটিভ অ্যাপ থেকে কোনো ত্রুটি ধরা এবং জাভাস্ক্রিপ্ট সাইডে তাদের ফেরত পাঠাতে ভুলবেন না।
WebView এর সাথে ইন্টিগ্রেট করুন
এই বিভাগটি বর্ণনা করে কিভাবে আপনার WebView ইন্টিগ্রেশন সেট আপ করবেন।
ওয়েবভিউ আরম্ভ করুন
আপনার অ্যান্ড্রয়েড অ্যাপের কার্যকলাপে, একটি WebView
শুরু করুন এবং একটি সহগামী WebViewClient
সেট আপ করুন। WebViewClient
WebView
এ ইনজেক্ট করা JavaScript কোডের সাথে যোগাযোগ পরিচালনা করে।
WebView সেট আপ করুন এবং ক্রেডেনশিয়াল ম্যানেজারকে কল করুন:
কোটলিন
val credentialManagerHandler = CredentialManagerHandler(this)
// ...
AndroidView(factory = {
WebView(it).apply {
settings.javaScriptEnabled = true
// Test URL:
val url = "https://credman-web-test.glitch.me/"
val listenerSupported = WebViewFeature.isFeatureSupported(
WebViewFeature.WEB_MESSAGE_LISTENER
)
if (listenerSupported) {
// Inject local JavaScript that calls Credential Manager.
hookWebAuthnWithListener(this, this@MainActivity,
coroutineScope, credentialManagerHandler)
} else {
// Fallback routine for unsupported API levels.
}
loadUrl(url)
}
}
)
জাভা
// Example shown in the onCreate method of an Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = findViewById(R.id.web_view);
// Test URL:
String url = "https://credman-web-test.glitch.me/";
Boolean listenerSupported = WebViewFeature.isFeatureSupported(
WebViewFeature.WEB_MESSAGE_LISTENER
);
if (listenerSupported) {
// Inject local JavaScript that calls Credential Manager.
hookWebAuthnWithListener(webView, this,
coroutineScope, credentialManagerHandler)
} else {
// Fallback routine for unsupported API levels.
}
webView.loadUrl(url);
}
একটি নতুন WebView ক্লায়েন্ট অবজেক্ট তৈরি করুন এবং ওয়েব পৃষ্ঠায় জাভাস্ক্রিপ্ট ইনজেক্ট করুন:
কোটলিন
// This is an example call into hookWebAuthnWithListener
val passkeyWebListener = PasskeyWebListener(
activity, coroutineScope, credentialManagerHandler
)
val webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
// Handle page load events
passkeyWebListener.onPageStarted();
webView.evaluateJavascript(PasskeyWebListener.INJECTED_VAL, null)
}
}
webView.webViewClient = webViewClient
জাভা
// This is an example call into hookWebAuthnWithListener
PasskeyWebListener passkeyWebListener = new PasskeyWebListener(
activity, credentialManagerHandler
)
WebViewClient webiewClient = new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
// Handle page load events
passkeyWebListener.onPageStarted();
webView.evaulateJavascript(PasskeyWebListener.INJECTED_VAL, null);
}
};
webView.setWebViewClient(webViewClient);
একটি ওয়েব বার্তা শ্রোতা সেট আপ করুন
JavaScript এবং Android অ্যাপের মধ্যে বার্তাগুলি পোস্ট করার অনুমতি দিতে, WebViewCompat.addWebMessageListener
পদ্ধতির সাথে একটি ওয়েব বার্তা শ্রোতা সেট আপ করুন৷
কোটলিন
val rules = setOf("*")
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
WebViewCompat.addWebMessageListener(
webView, PasskeyWebListener.INTERFACE_NAME, rules, passkeyWebListener
)
}
জাভা
Set<String> rules = new HashSet<>(Arrays.asList("*"));
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
WebViewCompat.addWebMessageListener(
webView, PasskeyWebListener.INTERFACE_NAME, rules, passkeyWebListener
)
}
ওয়েব ইন্টিগ্রেশন
কিভাবে ওয়েব ইন্টিগ্রেশন চেকআউট তৈরি করতে হয় তা শিখতে পাসওয়ার্ডহীন লগইনের জন্য একটি পাসকি তৈরি করুন এবং ফর্ম অটোফিলের মাধ্যমে একটি পাসকি দিয়ে সাইন ইন করুন ।
পরীক্ষা এবং স্থাপনা
অ্যান্ড্রয়েড অ্যাপ, ওয়েব পৃষ্ঠা এবং ব্যাকএন্ডের মধ্যে সঠিক যোগাযোগ নিশ্চিত করতে একটি নিয়ন্ত্রিত পরিবেশে সম্পূর্ণ প্রবাহটি পুঙ্খানুপুঙ্খভাবে পরীক্ষা করুন।
উৎপাদনে সমন্বিত সমাধান স্থাপন করুন, নিশ্চিত করুন যে ব্যাকএন্ড ইনকামিং রেজিস্ট্রেশন এবং প্রমাণীকরণ অনুরোধগুলি পরিচালনা করতে পারে। ব্যাকএন্ড কোডটি রেজিস্ট্রেশন (তৈরি করা) এবং প্রমাণীকরণ (পাওয়া) প্রক্রিয়াগুলির জন্য প্রাথমিক JSON তৈরি করা উচিত। এটি ওয়েব পৃষ্ঠা থেকে প্রাপ্ত প্রতিক্রিয়াগুলির বৈধতা এবং যাচাইকরণও পরিচালনা করবে।
যাচাই করুন বাস্তবায়ন UX সুপারিশের সাথে মিলে যায়।
গুরুত্বপূর্ণ নোট
-
navigator.credentials.create()
এবংnavigator.credentials.get()
অপারেশন পরিচালনা করতে প্রদত্ত জাভাস্ক্রিপ্ট কোড ব্যবহার করুন। -
PasskeyWebListener
ক্লাস হল Android অ্যাপ এবং WebView-এ JavaScript কোডের মধ্যে সেতু। এটি বার্তা প্রেরণ, যোগাযোগ এবং প্রয়োজনীয় ক্রিয়া সম্পাদন পরিচালনা করে। - প্রদত্ত কোড স্নিপেটগুলিকে আপনার প্রকল্পের কাঠামো, নামকরণের নিয়মাবলী এবং আপনার যে কোনো নির্দিষ্ট প্রয়োজনীয়তার সাথে মানানসই করে নিন।
- নেটিভ অ্যাপের দিক থেকে ত্রুটিগুলি ধরুন এবং সেগুলিকে জাভাস্ক্রিপ্টের দিকে ফেরত পাঠান৷
এই নির্দেশিকা অনুসরণ করে এবং WebView ব্যবহার করে এমন আপনার Android অ্যাপে ক্রেডেনশিয়াল ম্যানেজার API-কে একীভূত করার মাধ্যমে, আপনি আপনার ব্যবহারকারীদের তাদের শংসাপত্রগুলি কার্যকরভাবে পরিচালনা করার সময় তাদের জন্য একটি নিরাপদ এবং বিরামহীন পাসকি-সক্ষম লগইন অভিজ্ঞতা প্রদান করতে পারেন।