Burak Karaduman yazdı.
Figure 1: Photo by rc.xyz
¶İçerik
¶OTP nedir?
OTP, bir oturum açarken ya da işlem yaparken kullanılan ve sadece bir kez geçerli olan bir güvenlik kodudur. Bu kod, sahte istekleri engelleyip güvenliği artırmak için ekstra bir koruma sağlar.
Böylece her giriş ya da işlem, benzersiz bir kodla doğrulanmış olur. Bu da hem yetkisiz erişimi önlemeye hem de sahte işlemlerin önüne geçmeye yardımcı oluyor.
¶Androidde otomatik OTP Okumak
Direkt olarak kodlara erişmek isterseniz kütüphane olarak auto-read-otp projesinde bulabilirsiniz.
Otomatik OTP mesaj okuma entegrasyonuna başlamak için önce uygun API’yi seçmemiz gerekiyor.
Google bize iki seçenek sunuyor ve aşağıdaki görsel, hangisini kullanacağımıza karar vermemize yardımcı olacak.
Figure 2: OTP Reader APIs Provided by Google
Genellikle OTP’ler 4, 5 veya 6 haneli olur. Gönderenin kişinin rehberde kayıtlı olmasına gerek olmadığından ve OTP’yi onaylamak için kullanıcı etkileşimi istediğimizden dolayı SMS User Consent API daha uygun diyebiliriz.
Öncelikle SMS User Consent API için gerekli olan Google Mobile Services kütüphanesini eklememiz gerek. Güncel versiyonu buradan bulabilirsiniz.
implementation("com.google.android.gms:play-services-auth:21.2.0")
SMS Retriever’ın dinleme işlemini başlatmamız gerekiyor. Gereksiz yeniden başlatmaları önlemek için bu işlemi LaunchedEffect
bloğu içerisinde gerçekleştiriyoruz.
LaunchedEffect(key1 = true) {
SmsRetriever
.getClient(context)
.startSmsUserConsent(null)
.addOnSuccessListener {
shouldRegisterReceiver = true
}
}
SMS Consent API üzerinden gelen sonucu işlemek için ActivityResultLauncher ile SMS’i almamız gerek.
Bu launcher, SMS Retriever’dan gelen sonucu dinler. Onay öncesinde filtreleme ile kodu alıp kullanıcıya onay için gösterebiliriz. Onay verildiyse doğrulama kodunu alır ve normal bir senaryoda kod ekranda kullanıcı aksiyonu beklenmeden gösterilir.
private fun getVerificationCodeFromSms(message: String, smsCodeLength: Int): String =
message.filter { it.isDigit() }.take(smsCodeLength)
Bir broadcast receiver kaydetmemiz ve receiver’ın yaşam döngüsünü yönetmemiz gerekiyor.
Bir composable bileşeni compositiondan çıktığında broadcast receivera ihtiyacımız olmadığından kaydı kaldırmamıza olanak tanıyan DisposableEffect’i kullanmalıyız.
@Composable
internal fun SystemBroadcastReceiver(
systemAction: String,
onSystemEvent: (intent: Intent?) -> Unit,
) {
val context = LocalContext.current
val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)
// If either context or systemAction changes, unregister and register again
DisposableEffect(context, systemAction) {
val intentFilter = IntentFilter(systemAction)
val broadcast = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
currentOnSystemEvent(intent)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(broadcast, intentFilter, RECEIVER_EXPORTED)
} else {
context.registerReceiver(broadcast, intentFilter)
}
// Unregister the broadcast receiver when the effect leaves the Composition
onDispose {
context.unregisterReceiver(broadcast)
}
}
}
Gelen intent’i işlememiz ve gerekiyorsa onay penceresini tetiklememiz gerekiyor.
SystemBroadcastReceiver, bize alınan SMS’i sağlayacak.
if (shouldRegisterReceiver) {
SystemBroadcastReceiver(systemAction = SmsRetriever.SMS_RETRIEVED_ACTION) { intent ->
if (intent != null && SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.parcelable<Status>(SmsRetriever.EXTRA_STATUS) as Status
when (smsRetrieverStatus.statusCode) {
CommonStatusCodes.SUCCESS -> {
val consentIntent = extras.parcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
try {
// Start consent dialog. Timeout intent will be sent after 5 minutes
consentIntent?.let { launcher.launch(it) }
} catch (e: ActivityNotFoundException) {
onError(context.getString(R.string.activity_not_found_error))
}
}
CommonStatusCodes.TIMEOUT -> onError(context.getString(R.string.sms_timeout_error))
}
}
}
}
getParcelable fonksiyonu artık deprecated olduğundan dolayı Bundle için BundleCompat kullanarak bunu alacak basit bir extension fonksiyonu yazabiliriz.
internal inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? =
BundleCompat.getParcelable(this, key, T::class.java)
Artık SMS Consent API’sini kullanmaya hazırız. Yukarıdaki kodları bir composable fonksiyonu içerisinde birleştirebiliriz.
@Composable
fun SmsUserConsent(
smsCodeLength: Int,
onOTPReceived: (otp: String) -> Unit,
onError: (error: String) -> Unit,
) {
val context = LocalContext.current
var shouldRegisterReceiver by remember { mutableStateOf(false) }
LaunchedEffect(key1 = true) {
SmsRetriever
.getClient(context)
.startSmsUserConsent(null)
.addOnSuccessListener {
shouldRegisterReceiver = true
}
}
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK && it.data != null) {
val message: String? = it.data!!.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE)
message?.let {
val verificationCode = getVerificationCodeFromSms(message, smsCodeLength)
onOTPReceived(verificationCode)
}
shouldRegisterReceiver = false
} else {
onError(context.getString(R.string.sms_retriever_error_consent_denied))
}
}
if (shouldRegisterReceiver) {
SystemBroadcastReceiver(systemAction = SmsRetriever.SMS_RETRIEVED_ACTION) { intent ->
if (intent != null && SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.parcelable<Status>(SmsRetriever.EXTRA_STATUS) as Status
when (smsRetrieverStatus.statusCode) {
CommonStatusCodes.SUCCESS -> {
val consentIntent = extras.parcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
try {
// Start consent dialog. Timeout intent will be sent after 5 minutes
consentIntent?.let { launcher.launch(it) }
} catch (e: ActivityNotFoundException) {
onError(context.getString(R.string.activity_not_found_error))
}
}
CommonStatusCodes.TIMEOUT -> onError(context.getString(R.string.sms_timeout_error))
}
}
}
}
}
Bunu uyguluyorsanız muhtemelen bir OTP Doğrulama Ekranınız vardır. VerificationScreen composable kullanarak örnek bir uygulamayı burada bulabilirsiniz.
Çok uzun değil ama bunlarla uğraşmak istemiyorsanız bunlara bir kütüphane olarak da erişebilirsiniz.