1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DENSOAdvent Calendar 2024

Day 5

Androidアプリに生体認証を導入する Compose編

Last updated at Posted at 2024-12-04

概要

生体認証ダイアログを表示する Android Developers

・公式サイトにもあるような、生体認証ダイアログを用いた認証を作成します。
このダイアログでは

  • 指紋認証
  • 顔認証
  • 虹彩認証

など、端末のロック解除方法に登録してあるものを使用して、ユーザ認証が行えます。

・公式サイト内ではAndroid Viewを使用していますが、今回はせっかくなのでJetpack Composeで作成します。

開発環境

・Macbook Pro (Apple M2 Pro)
・macOS Sonoma
・Android Studio Ladybug 2024.2.1 Patch2
実行端末
・Google Pixel9 (Android 14)

エミュレータではテストができないため、Android実機端末が必要です

※完成画像などが使用できていないのもこのためです。実機のスクショがうまく出来ませんでした...

実装方法

プロジェクトの作成などは各自で行ってください。

1.必要なライブラリを追加する

・認証機能の実装には、androidx.biometricが必要なので、これを追加します。

build.gradle.kts

 implementation("androidx.biometric:biometric-ktx:1.4.0-alpha02")

※2024/12/02時点の最新版(安定版は1.1.0)

2.認証ダイアログの作成

Composableで作成します

@Composable
fun BiometricAuthenticationDialog(
    onAuthSuccess: () -> Unit,
    onAuthError: (String) -> Unit
) {
    val context = LocalContext.current
    val executor = ContextCompat.getMainExecutor(context)

    
    val biometricPrompt = remember {
        BiometricPrompt(
            context as FragmentActivity,
            executor,
            object : BiometricPrompt.AuthenticationCallback() {
                //if auth is successful.
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    super.onAuthenticationSucceeded(result)
                    onAuthSuccess()
                }
                //if auth is error.
                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    super.onAuthenticationError(errorCode, errString)
                    onAuthError(errString.toString())
                }
                //if auth is failed.
                override fun onAuthenticationFailed() {
                    super.onAuthenticationFailed()
                    onAuthError("Authentication failed.")
                }
            }
        )
    }

    //ダイアログに表示するタイトルやテキストをセットする
    LaunchedEffect(Unit) {
        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Authentication")
            .setSubtitle("Please touch your finger")
            .setNegativeButtonText("Cancel")
            .build()
        biometricPrompt.authenticate(promptInfo)
    }
}

これに加えて、この関数を呼び出す親のアクティビティの継承元を
ComponentActivityからFragmentActivityに変更しておきます。(理由は後述)

class MainActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            Favorite_actor_appTheme {
                MyApp()
            }
        }
    }
}

解説

作成したBiometricAuthenticationDialogは二つの引数を持たせます。

  • onAuthSuccess: 認証が成功した時の動作を定義する関数
  • onAuthError:認証に失敗した時の動作を定義する関数

onAuthErrorには、エラー内容を渡すため、String型の引数を定義しておきます

 val context = LocalContext.current
 val executor = ContextCompat.getMainExecutor(context)

この後作成する認証時の動作を定義するBIometricPromptの引数としてFragmentActivityexecutorが必要なため、あらかじめ定義しておきます。

exexutorは処理を行うスレッドやタスクを管理するためのもので、今回はメインスレッドで処理を行う必要があるため、上記のように定義しておきます。

val biometricPrompt = remember {
        BiometricPrompt(
            context as FragmentActivity,
            executor,
            object : BiometricPrompt.AuthenticationCallback() {
                //if auth is successful.
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    super.onAuthenticationSucceeded(result)
                    onAuthSuccess()
                }
                //if auth is error.
                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    super.onAuthenticationError(errorCode, errString)
                    onAuthError(errString.toString())
                }
                //if auth is failed.
                override fun onAuthenticationFailed() {
                    super.onAuthenticationFailed()
                    onAuthError("Authentication failed.")
                }
            }
        )
    }

生体認証の動作の核となるBiometricPromptを定義します

BiometricPromptメソッドの実装は以下のようになっています

 public BiometricPrompt(
            @NonNull FragmentActivity activity,
            @NonNull Executor executor,
            @NonNull AuthenticationCallback callback) {

        if (activity == null) {
            throw new IllegalArgumentException("FragmentActivity must not be null.");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Executor must not be null.");
        }
        if (callback == null) {
            throw new IllegalArgumentException("AuthenticationCallback must not be null.");
        }

        final FragmentManager fragmentManager = activity.getSupportFragmentManager();
        final BiometricViewModel viewModel =
                new ViewModelProvider(activity).get(BiometricViewModel.class);
        init(true /* hostedInActivity */, fragmentManager, viewModel, executor, callback);
    }

このため、まず第1引数としてFragmentActivityを渡す必要があります。
すでに先ほど現在のコンテキストを取得しているため、

context as FragmentActivity,

として渡します。

先ほど、呼び出し先の親アクティビティをFragmentActivityに変更したのはこのためです。
ここで親アクティビティがComponentActivityを使用していると、実行時にエラーが発生します。

第2引数には先ほど定義したexeutorを渡します。

そして最後に第3引数として、認証時の動作を定義するコールバックを作成します。

object : BiometricPrompt.AuthenticationCallback() {
                //if auth is successful.
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    super.onAuthenticationSucceeded(result)
                    onAuthSuccess()
                }
                //if auth is error.
                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    super.onAuthenticationError(errorCode, errString)
                    onAuthError(errString.toString())
                }
                //if auth is failed.
                override fun onAuthenticationFailed() {
                    super.onAuthenticationFailed()
                    onAuthError("Authentication failed.")
                }
            }

見ての通り、BiometricPrompt.AuthenticationCallbackには3つのメソッドがあり、

  • onAuthenticationSucceededには成功時の動作
  • onAuthenticationErrorにはなんらかのエラーが発生した時の動作
  • onAuthenticatoinFailedには認証失敗時の動作

をそれぞれ定義します。

あとは、認証ダイアログに表示される文字などを定義して、ダイアログを表示させます。

 LaunchedEffect(Unit) {
        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            //ダイアログ上部に表示されるタイトル文字列
            .setTitle("Authentication")
            //タイトル下に表示される文字列
            .setSubtitle("Please touch your finger")
            //ダイアログ左下に表示されるネガティブボタンの文字(主にキャンセル用)
            .setNegativeButtonText("Cancel")
            .build()
        //ダイアログを表示
        biometricPrompt.authenticate(promptInfo)
    }

3.呼び出し

ここまでこれば、作成したBiometricAuthenticationDialogComposableを呼び出すだけです。

@Composable
fun TopPage(modifier: Modifier = Modifier) {

    var showBiometricDialog by remember { mutableStateOf(false) }
    
    var biometricResult by remember { mutableStateOf<String?>(null) }

    if (showBiometricDialog) {
        BiometricAuthenticationDialog(
            onAuthSuccess = { biometricResult = "Success" },
            onAuthError = { error ->
                biometricResult = error
                showBiometricDialog = false
            }
        )
    }

    Box(
        modifier = modifier.fillMaxSize(),
    ) {
        Image(
            painter = painterResource(R.drawable.actor),
            contentDescription = "background",
            contentScale = ContentScale.Crop
        )
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(color = colorResource(R.color.dark_effect))
        )
        Text(
            "",
            style = TextStyle(color = colorResource(R.color.white), fontSize = 30.sp),
            modifier = Modifier.align(
                Alignment.Center
            )
        )
        Button(
            onClick = { showBiometricDialog = true }, modifier = Modifier
                .align(Alignment.BottomCenter)
                .padding(bottom = 200.dp)
        ) {
            Text("Authorization and Start")
        }

    }
}

この例では、ボタンが押されるとshowBiometricDialog変数がtrueになり、ダイアログが呼び出されます。
また、認証結果が成功の場合はbiometricResultSuccessが代入され、失敗時は受け取ったエラー文が代入されるようになっています。

実際にはこの後navControllerなどを使用して画面遷移などを行うことになると思います。

実装は以上です。

まとめ

作成してみた感想としては、意外と簡単でシンプルな印象を持ちました。
インターネット接続なども必要なく、お手軽ながらもしっかりした認証を作成できて自分はとても好印象でした!

参考にしたサイトなど

生体認証ダイアログを表示する Android Developers

Biometric | Jetpack| Android Developers

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?