この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
アプリがSMSから認証コードを取得して自動入力するSMS User Consent APIを使ってみた。
はじめに
サービスのセキュリティを高める方法としてSMSを利用した本人認証があります。SMS認証はセキュリティが向上する反面、電話番号と認証コードの入力が必要なためユーザーにとっては煩わしいです。当記事では、ユーザーの負担を減らすための手段としてSMS User Consent APIを紹介します。このAPIを使うことでパーミッションの追加なしでSMS受信時にユーザーがワンタップするだけでアプリはSMSから認証コードを取得することができます。(サーバーサイドとSMS Retriever APIを組み合わせることで完全に自動で認証コードを取得することが出来ますが、今回は扱いません)
今回実装したソースコードはこちら↓
- (GitHub)sms-user-consent-api-sample
開発環境
- OS: macOS Catalina
- Android Studio: 4.1.2
- Language: Kotlin 1.4.21
制限事項
SMS User Consent APIには、いくつかの制限があります。以下のケースではSMSを受信してもユーザーに許可を求めるダイアログは表示されません。
- SMSに数字を1つ以上含んでいる4~10桁の英数字が存在しない
- ユーザーの連絡先に登録されている差出人からのメッセージ
実装手順
- ライブラリを追加する
- レイアウトファイルの編集
- MainActivityの実装
アプリレベルのbuild.gradleにライブラリを追加
アプリレベルのbuild.gradleに以下に示した2つのライブラリを追加します。
app/build.gradle
dependencies {
// 省略
implementation "com.google.android.gms:play-services-auth:19.0.0"
implementation "com.google.android.gms:play-services-auth-api-phone:17.5.0"
}
レイアウトの編集
activity_main.xmlに認証コードを入力するEditTextを配置します。
src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/editTextTextPersonName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Enter verification code"
android:inputType="text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivityの編集
SMS受信時のおおまかな流れを説明します。端末がSMSを受信するとシステムがBroadcastReceiverのonReceiveメソッドを呼び出します。startActivityForResultメソッドを使ってアプリ上に確認ダイアログを表示します。ユーザーが確認ダイアログのAllowボタンをタップするとonActivityResultメソッドが呼び出されます。ここでSMSメッセージを解析して認証コードを取得します。今回のサンプルアプリではEditTextに取得した認証コードを表示しましたが、代わりにサーバーサイドへ認証コードを送信することでさらにユーザー操作を減らすことができます。
src/main/java/com/mos1210/android/example/smsuserconsent/MainActivity.kt
import android.app.Activity
import android.content.*
import android.os.Bundle
import android.util.Log
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
class MainActivity : AppCompatActivity() {
companion object {
val TAG: String = MainActivity::class.java.simpleName
private const val REQUEST_CODE = 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
registerReceiver(smsVerificationReceiver, intentFilter)
// SMSメッセージの待ち受け開始
SmsRetriever.getClient(this).startSmsUserConsent(null)
}
override fun onPause() {
super.onPause()
// SMSメッセージの待ち受け解除
unregisterReceiver(smsVerificationReceiver)
}
private val smsVerificationReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action
&& intent.extras != null
) {
val status = intent.extras?.get(SmsRetriever.EXTRA_STATUS) as Status
when (status.statusCode) {
CommonStatusCodes.SUCCESS -> {
val consentIntent =
intent.extras?.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
try {
startActivityForResult(consentIntent, REQUEST_CODE)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
}
}
CommonStatusCodes.TIMEOUT -> {
Log.d(TAG, "timeout")
// 5分でタイムアウト
}
}
}
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE ->
if (resultCode == Activity.RESULT_OK && data != null) {
val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE) ?: return
val oneTimeCode = parseOneTimeCode(message)
findViewById<EditText>(R.id.editTextVerificationCode).setText(oneTimeCode)
}
}
}
private fun parseOneTimeCode(message: String): String {
// SMSメッセージに合わせて文字列を処理する
return message.split("\n")[0].split(":")[1]
}
}
検証方法
エミューレーターに対してadbコマンドでSMSを送信します。最初にエミュレータを起動してからTerminalでadb devices
コマンドを入力しテスト対象となるデバイスを調べます。
$ adb devices
List of devices attached
emulator-5554 device ←対象のエミュレーター
次に、以下のadbコマンドで対象のEmulatorにSMSを送信します。この例では電話番号が「09012345678」、SMSの内容が「verification code:123456」です。
$ adb -s emulator-5554 emu sms send 09012345678 verification code:123456
OK
実行結果
SMS受信時に確認ダイアログが表示され、ユーザーがAllowボタンをタップすると認証コードが自動入力されました。
まとめ
SMS User Consent APIを使ってSMSから認証コードを取得して自動入力することができました。もっと別な方法があるよ!などあればTwitterやコメントで教えていただければ嬉しいです。