패스키로 로그인

이 가이드에서는 인증을 위해 패스키를 사용하는 구현을 계속 설명합니다. 사용자가 패스키로 로그인할 수 있으려면 패스키 만들기의 안내도 완료해야 합니다.

패스키로 인증하려면 먼저 앱 서버에서 공개 키를 가져오는 데 필요한 옵션을 가져온 다음 Credential Manager API를 호출하여 공개 키를 가져와야 합니다. 그런 다음 로그인 응답을 적절하게 처리합니다.

개요

이 가이드에서는 패스키로 사용자를 로그인하기 위해 클라이언트 앱에서 필요한 변경사항에 중점을 두고 앱 서버 측 구현에 관한 간략한 개요를 제공합니다. 서버 측 통합에 대해 자세히 알아보려면 서버 측 패스키 인증을 참고하세요.

사용자 계정과 연결된 패스키 및 비밀번호 옵션을 모두 가져오려면 다음 단계를 따르세요.

  1. 서버에서 사용자 인증 정보 요청 옵션 가져오기: 앱에서 인증 서버로 요청하여 패스키 로그인 프로세스를 시작합니다. 서버에서 공개 키 사용자 인증 정보를 가져오는 데 필요한 옵션과 고유한 챌린지를 전송합니다.
  2. 공개 키 사용자 인증 정보를 가져오는 데 필요한 객체 만들기: 서버에서 전송한 옵션을 GetPublicKeyCredentialOption 객체로 래핑합니다.
  3. (선택사항) getCredential 준비: Android 14 이상에서는 getCredential()을 호출하기 전에 prepareGetCredential() 메서드를 사용하여 계정 선택기를 표시하여 지연 시간을 줄일 수 있습니다.
  4. 로그인 흐름 시작: getCredential() 메서드를 호출하여 사용자를 로그인시킵니다.
  5. 응답 처리: 가능한 각 사용자 인증 정보 응답을 처리합니다.
  6. 예외 처리: 예외를 적절하게 처리해야 합니다.

1. 서버에서 사용자 인증 정보 요청 옵션 가져오기

공개 키 사용자 인증 정보를 가져오는 데 필요한 옵션과 각 로그인 시도마다 고유한 challenge를 서버에 요청합니다. 서버 측 구현에 대해 자세히 알아보려면 챌린지 만들기인증 요청 옵션 만들기를 참고하세요.

옵션은 다음과 유사합니다.

{
  "challenge": "<your app challenge>",
  "allowCredentials": [],
  "rpId": "<your app server domain>"
}

필드에 관해 자세히 알아보려면 패스키로 로그인에 관한 블로그 게시물을 참고하세요.

2. 공개 키 사용자 인증 정보를 가져오는 데 필요한 객체 만들기

앱에서 옵션을 사용하여 GetPublicKeyCredentialOption 객체를 만듭니다. 다음 예에서 requestJson는 서버에서 전송한 옵션을 나타냅니다.

// Get password logins from the credential provider on the user's device.
val getPasswordOption = GetPasswordOption()

// Get passkeys from the credential provider on the user's device.
val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
    requestJson = requestJson
)

그런 다음 GetPublicKeyCredentialOptionGetCredentialRequest 객체로 래핑합니다.

val credentialRequest = GetCredentialRequest(
    // Include all the sign-in options that your app supports.
    listOf(getPasswordOption, getPublicKeyCredentialOption),
    // Defines whether you prefer to use only immediately available
    // credentials or hybrid credentials.
    preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
)

3. 선택사항: 로그인 지연 시간 줄이기

Android 14 이상에서는 getCredential()을 호출하기 전에 prepareGetCredential() 메서드를 사용하여 계정 선택기를 표시할 때 지연 시간을 줄일 수 있습니다.

prepareGetCredential() 메서드는 캐시된 PrepareGetCredentialResponse 객체를 반환합니다. 이렇게 하면 다음 단계의 getCredential() 메서드가 캐시된 데이터로 계정 선택기를 표시할 수 있습니다.

coroutineScope {
    val response = credentialManager.prepareGetCredential(
        GetCredentialRequest(
            listOf(
                // Include all the sign-in options that your app supports
                getPublicKeyCredentialOption, 
                getPasswordOption
            )
        )
    )
}

4. 로그인 흐름 시작

getCredential() 메서드를 호출하여 사용자에게 계정 선택기를 표시합니다. 다음 코드 스니펫을 참고하여 로그인 흐름을 시작하세요.

coroutineScope {
    try {
        result = credentialManager.getCredential(
            // Use an activity-based context to avoid undefined system UI
            // launching behavior.
            context = activityContext,
            request = credentialRequest
        )
        handleSignIn(result)
    } catch (e: GetCredentialException) {
        // Handle failure
    }
}

5. 응답 처리

다양한 유형의 사용자 인증 정보 객체 중 하나를 포함할 수 있는 응답을 처리합니다.

fun handleSignIn(result: GetCredentialResponse) {
    // Handle the successfully returned credential.
    val credential = result.credential

    when (credential) {
        is PublicKeyCredential -> {
            val responseJson = credential.authenticationResponseJson
            // Share responseJson i.e. a GetCredentialResponse on your server to
            // validate and  authenticate
        }

        is PasswordCredential -> {
            val username = credential.id
            val password = credential.password
            // Use id and password to send to your server to validate
            // and authenticate
        }

        is CustomCredential -> {
            // If you are also using any external sign-in libraries, parse them
            // here with the utility functions provided.
            if (credential.type == ExampleCustomCredential.TYPE) {
                try {
                    val ExampleCustomCredential =
                        ExampleCustomCredential.createFrom(credential.data)
                    // Extract the required credentials and complete the authentication as per
                    // the federated sign in or any external sign in library flow
                } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
                    // Unlikely to happen. If it does, you likely need to update the dependency
                    // version of your external sign-in library.
                    Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
                }
            } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential")
            }
        }
        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential")
        }
    }
}

인증에서 반환된 PublicKeyCredential는 기본적으로 다음과 같이 구조화된 서명된 어설션입니다.

{
  "id": "<credential ID>",
  "type": "public-key",
  "rawId": "<raw credential ID>",
  "response": {
    "clientDataJSON": "<signed client data containing challenge>",
    "authenticatorData": "<authenticator metadata>",
    "signature": "<digital signature to be verified>",
    "userHandle": "<user ID from credential registration>"
  }
}

서버에서 사용자 인증 정보를 확인해야 합니다. 자세한 내용은 사용자 확인 및 로그인을 참고하세요.

6. 예외 처리

GetCredentialException의 모든 하위 클래스 예외를 처리해야 합니다. 각 예외를 처리하는 방법을 알아보려면 문제 해결 가이드를 참고하세요.

coroutineScope {
    try {
        result = credentialManager.getCredential(
            context = activityContext,
            request = credentialRequest
        )
    } catch (e: GetCredentialException) {
        Log.e("CredentialManager", "No credential available", e)
    }
}

다음 단계