종속 항목 추가

앱의 build.gradle 파일에서 인증 관리자의 다음 종속 항목을 추가합니다.

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha02")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha02"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02"
}

인증 관리자 초기화

앱 또는 활동 컨텍스트를 사용하여 CredentialManager 객체를 만듭니다.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

디지털 사용자 인증 정보 요청 구성

인증된 이메일을 요청하려면 GetDigitalCredentialOption이 포함된 GetCredentialRequest을 구성합니다. 이 옵션에는 OpenID for Verifiable Presentations(OpenID4VP) 요청으로 형식이 지정된 requestJson 문자열이 필요합니다.

OpenID4VP 요청 JSON은 특정 구조를 따라야 합니다. 현재 제공업체는 외부 "digital": {"requests": [...]} 래퍼가 있는 JSON 구조를 지원합니다.

    val nonce = generateSecureRandomNonce()

    // This request follows the OpenID4VP spec
    val openId4vpRequest = """
{
  "requests": [
    {
      "protocol": "openid4vp-v1-unsigned",
      "data": {
        "response_type": "vp_token",
        "response_mode": "dc_api",
        "nonce": "$nonce",
        "dcql_query": {
          "credentials": [
            {
              "id": "user_info_query",
              "format": "dc+sd-jwt",
               "meta": { 
                  "vct_values": ["UserInfoCredential"] 
               },
              "claims": [ 
                {"path": ["email"]}, 
                {"path": ["name"]},  
                {"path": ["given_name"]},
                {"path": ["family_name"]},
                {"path": ["picture"]},
                {"path": ["hd"]},
                {"path": ["email_verified"]}
              ]
            }
          ]
        }
      }
    }
  ]
}
"""

    val getDigitalCredentialOption = GetDigitalCredentialOption(requestJson = openId4vpRequest)
    val request = GetCredentialRequest(listOf(getDigitalCredentialOption))

그런 다음 openId4vpRequest JSON을 GetDigitalCredentialOption로 래핑하고 GetCredentialRequest를 만들어 getCredential()를 호출합니다.

사용자에게 요청을 표시합니다.

인증 관리자 기본 제공 UI를 사용하여 사용자에게 요청을 표시합니다.

try {
    // Requesting Digital Credential from user...
    val result = credentialManager.getCredential(activity, request)

    when (val credential = result.credential) {
        is DigitalCredential -> {
            val responseJsonString = credential.credentialJson

            // Successfully received digital credential response.

            // Next, parse this response and send it to your server.
            // ...
        }

        else -> {
            // handle Unexpected State() - Up to the developer
        }
    }
} catch (e: Exception) {
    // handle exceptions - Up to the developer
}

클라이언트에서 응답 파싱

응답을 받은 후 클라이언트에서 예비 파싱을 실행할 수 있습니다. 이는 사용자 이름을 표시하는 등 UI를 즉시 업데이트하는 데 유용합니다.

다음 코드는 원시 선택적 공개 JWT(SD-JWT)를 추출하고 도우미를 사용하여 클레임을 디코딩합니다.

// 1. Parse the outer JSON wrapper to get the `vp_token`
val responseData = JSONObject(responseJsonString)
val vpToken = responseData.getJSONObject("vp_token")

// 2. Extract the raw SD-JWT string
val credentialId = vpToken.keys().next()
val rawSdJwt = vpToken.getJSONArray(credentialId).getString(0)

// 3. Use your parser to get the verified claims
// Server-side validation/parsing is highly recommended.

// Assumes a local parser like the one in our SdJwtParser.kt sample
val claims = SdJwtParser.parse(rawSdJwt)
Log.d("TAG", "Parsed Claims: ${claims.toString(2)}")

// 4. Create your VerifiedUserInfo object with REAL data
val userInfo = VerifiedUserInfo(
    email = claims.getString("email"),
    displayName = claims.optString("name", claims.getString("email"))
)

응답 처리

Credential Manager API는 DigitalCredential 응답을 반환합니다.

다음은 원시 responseJsonString의 모습과 검증된 이메일과 함께 추가 메타데이터를 가져오는 내부 SD-JWT를 파싱한 후의 클레임의 모습의 예입니다.

/*
// Example of the raw JSON response from credential.credentialJson:
{
  "vp_token": {
    // This key matches the 'id' you set in your dcql_query
    "user_info_query": [
      // The SD-JWT string (Issuer JWT ~ Disclosures ~ Key Binding JWT)
      "eyJhbGciOiJ...~WyI...IiwgImVtYWlsIiwgInVzZXJAZXhhbXBsZS5jb20iXQ~...~eyJhbGciOiJ..."
    ]
  }
}

// Example of the parsed and verified claims from the SD-JWT on your server:
{
  "cnf": {
    "jwk": {..}
  },
  "exp": 1775688222,
  "iat": 1775083422,
  "iss": "https://verifiablecredentials-pa.googleapis.com",
  "vct": "UserInfoCredential",
  "email": "jane.doe.246745@gmail.com",
  "email_verified": true,
  "given_name": "Jane",
  "family_name": "Doe",
  "name": "Jane Doe",
  "picture": "http://example.com/janedoe/me.jpg",
  "hd": ""
}
 */

계정 생성 시 서버 측 유효성 검사

인증을 위해 애플리케이션은 계정을 만들거나 사용자를 로그인하기 전에 암호화 유효성 검사를 위해 전체 responseJsonString를 서버에 전송해야 합니다.

서버의 유효성 검사는 다음을 충족해야 합니다.

  • 발급자 확인: iss (발급자) 필드가 https://verifiablecredentials-pa.googleapis.com와 일치하는지 확인합니다.
  • 서명 확인: https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks에서 제공되는 공개 키 (JWK)를 사용하여 SD-JWT의 서명을 확인합니다.

완전한 보안을 위해 재생 공격을 방지하도록 nonce도 검증해야 합니다.

try {
    // Send the raw credential response and the original nonce to your server.
    // Your server must validate the response. createAccountWithVerifiedCredentials
    // is a custom implementation per each RP for server side verification and account creation.
    val serverResponse = createAccountWithVerifiedCredentials(responseJsonString, nonce)

    // Server returns the new account info (e.g., email, name)
    val claims = JSONObject(serverResponse.json)

    val userInfo = VerifiedUserInfo(
        email = claims.getString("email"),
        displayName = claims.optString("name", claims.getString("email"))
    )

    // handle response - Up to the developer
} catch (e: Exception) {
    // handle exceptions - Up to the developer
}

계정을 프로비저닝한 후 선택사항이지만 적극 권장되는 다음 단계는 해당 계정의 패스키를 즉시 만드는 것입니다. 이렇게 하면 사용자가 비밀번호 없이 안전하게 로그인할 수 있습니다. 이 흐름은 표준 패스키 등록과 동일합니다.

WebView에서 흐름이 작동하려면 개발자가 핸드오버를 용이하게 하는 JavaScript 브리지 (JS 브리지)를 구현해야 합니다. 이 브리지를 통해 WebView는 네이티브 앱에 신호를 보낼 수 있으며, 네이티브 앱은 Credential Manager API에 대한 실제 호출을 실행할 수 있습니다.