콘텐츠로 이동

2단계. 로그인

사용자가 별도의 회원가입 없이 Google 계정으로 앱에 로그인하도록 구현합니다. 시작하기 전에 1단계. 연동 환경 구성을 마치세요.

Google 계정 로그인은 앱이 실행되는 OS에 따라 자격 증명을 얻는 방법과 로그인 흐름이 다릅니다.

OS 자격 증명 획득 방법 동작 흐름
Android Google 로그인 Add-on Direct Token 흐름
iOS, macOS, Windows 웹 로그인 세션 Authorization Code 흐름

Android에서는 Add-on이 Hive Axyl 인증 서버에서 바로 검증할 수 있는 id_token을 반환하므로 교환 단계 없이 로그인합니다. 다른 OS에서는 웹 로그인 세션이 Google 인가 코드만 반환합니다. 외부 인증 코드 교환으로 인가 코드를 교환한 뒤 로그인합니다.

1. Google 자격 증명 획득

앱이 실행되는 OS에 맞는 방법으로 Google 자격 증명을 획득합니다.

1.1. Android

Android에서는 Google 로그인 Add-on이 OS의 계정 선택 화면을 띄우고, 사용자가 고른 계정의 id_token을 돌려줍니다.

nonce 준비

nonce는 Google id_token이 현재 로그인 요청에 맞게 발급되었는지 확인하는 일회성 난수입니다. 로그인 호출 직전에 앱에서 새로 생성해 Add-on에 전달하세요.

암호학적으로 안전한 난수 32바이트를 URL-safe base64로 인코딩하고 패딩을 제거한 문자열을 그대로 사용합니다. Google은 이 문자열을 id_token의 nonce 클레임에 그대로 담아 돌려주므로, Apple 로그인과 달리 해시로 변환하지 않습니다.

using System;
using System.Security.Cryptography;

// 암호학적으로 안전한 32바이트 난수를 URL-safe base64(패딩 제거)로 인코딩합니다.
static string CreateNonce()
{
    var bytes = new byte[32];
    using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(bytes);
    return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}

로그인 Add-on 호출

Method

LoginAsync

IAndroidCredentialManagerPlugin.LoginAsync()를 호출해 계정 선택 화면을 띄우고 자격 증명을 받습니다. 요청에는 화면에 제시할 인증 옵션을 하나 이상 담아야 하며, 같은 종류의 옵션을 두 번 담을 수 없습니다.

인증 옵션 화면 형태 사용 시점
SignInWithGoogle Google 로그인 버튼을 눌렀을 때 나타나는 계정 선택 화면 사용자가 로그인 버튼을 직접 눌렀을 때
GoogleId 화면 아래쪽에서 올라오는 계정 선택 시트 앱 진입 시 자동으로 로그인을 제안할 때

GoogleId 옵션은 후보 계정을 좁히는 설정을 추가로 제공합니다. FilterByAuthorizedAccounts를 true로 두면 사용자가 이 앱에 이미 사용을 허용한 Google 계정만 후보로 제시하고, AutoSelectEnabled를 true로 두면 그런 계정이 하나뿐일 때 화면 없이 바로 반환합니다.

using Hive.Axyl.Auth.Addon.CredentialManager;
using Hive.Axyl.Core;
using UnityEngine;

// Add-on은 Android 빌드에서만 등록됩니다.
if (!HiveCore.TryResolve<IAndroidCredentialManagerPlugin>(out var google))
{
    // Android가 아니거나 Add-on이 등록되지 않음 → 웹 로그인 세션으로 분기
    return;
}

string rawNonce = CreateNonce();

var loginResult = await google.LoginAsync(new LoginRequest {
    Options = new[] {
        new CredentialOption {
            SignInWithGoogle = new SignInWithGoogleOption {
                WebClientId = "{webClientId}",
                Nonce       = rawNonce,
            },
        },
    },
});

string googleProviderToken;    // 로그인 요청의 ProviderToken
string googleProviderUserId;   // 로그인 요청의 ProviderUserId
switch (loginResult)
{
    case AndroidCredentialManagerServiceLoginResult.Success success
        when success.Data.Selected.GoogleIdToken is { } credential:
        googleProviderToken  = credential.IdToken;
        googleProviderUserId = credential.UniqueId;
        break;

    case AndroidCredentialManagerServiceLoginResult.UserCanceled:
        // 사용자가 계정 선택 화면을 닫음 → 로그인 화면 유지
        return;

    case AndroidCredentialManagerServiceLoginResult.NoCredentials:
        // 기기에 사용할 수 있는 Google 계정이 없음 → 계정 추가 안내
        return;

    case AndroidCredentialManagerServiceLoginResult.Failure failure:
        HiveError err = failure.Problem;
        Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
        return;

    // 안전망: 알 수 없는 신규 결과(UnknownOutcome)와 Google 자격 증명이 아닌 Success
    default:
        Debug.LogWarning($"처리되지 않은 결과: {loginResult.GetType().Name}");
        return;
}

WebClientId에는 Google 로그인 인증 정보 확인에서 준비한 웹 애플리케이션 유형 OAuth 클라이언트 ID를 넣습니다.

Warning

사용자 식별자로는 UniqueId만 사용하세요. 같은 자격 증명의 Id는 계정의 이메일 주소이며 Google이 사용을 권장하지 않습니다. Hive Axyl 인증 서버는 ProviderUserId를 id_token의 사용자 식별자와 대조하므로, Id를 넣으면 로그인이 거부됩니다.

응답 상태

응답 케이스 설명 앱 클라이언트 대응
Success 자격 증명 획득 성공. Data.Selected.GoogleIdToken에서 값을 꺼냅니다. 외부 인증 제공자 로그인 진행
UserCanceled 사용자가 계정 선택 화면을 닫은 경우 로그인 화면 유지
NoCredentials 요청한 옵션에 맞는 Google 계정이 기기에 없는 경우 계정 추가 안내
UnknownOutcome 이 SDK 버전이 알지 못하는 신규 결과 로깅 후 보수적으로 처리
Failure 공통 Failure입니다. 앱이 취소한 경우(Cancelled)도 여기로 분기하며 원인은 Failure.Problem.Code에 담깁니다. 공통 오류 처리를 참조하세요. 공통 오류 처리 기준에 따라 처리

사용자가 로그인 화면을 벗어나는 등 앱이 먼저 인증을 중단해야 할 때는 CancelCurrentSession()을 호출합니다. 대기 중이던 LoginAsync()는 Failure로 끝나고 Failure.Problem.Code에 Cancelled가 담깁니다.

1.2. Android 외 OS

Android가 아닌 OS에서는 웹 로그인 세션으로 Google의 로그인 페이지를 열고 인가 코드를 받습니다. 인가 URL 구성과 응답 파라미터는 Google이 정한 OAuth 2.0 규격을 따르므로, 필요한 파라미터는 Google 공식 문서에서 확인하세요.

인가 URL 구성

인가 URL에 아래 값을 넣어야 로그인이 정상 동작합니다. 리다이렉트 URI를 앱이 수신하도록 준비하는 방법은 리다이렉트 URI 준비를 참조하세요.

state는 앱이 시작하지 않은 인증 결과를 걸러 내는 값이므로, 만든 값을 보관해 두었다가 콜백 확인에서 비교하세요. PKCE code_challenge를 넣었다면 짝이 되는 codeVerifier도 외부 인증 코드 교환 단계까지 앱에 보관하세요.

콜백 확인

웹 로그인 세션은 콜백 파라미터를 그대로 돌려줄 뿐 검증하지 않으므로, 콜백을 받으면 state부터 확인하세요. 콜백의 state가 보관한 값과 다르면 이번 로그인에서 시작한 응답이 아니므로 로그인을 중단합니다. state가 같더라도 error가 있거나 code가 없으면 로그인을 중단합니다. error가 access_denied이면 사용자가 로그인을 거절한 경우이고, 그 밖의 error는 로그인에 실패한 경우입니다.

외부 인증 코드 교환

콜백의 code를 외부 인증 코드 교환으로 보내 로그인에 사용할 자격 증명으로 바꿉니다. 교환 요청에는 아래 값을 넣습니다.

  • ProviderId: Provider.Google
  • ProviderCode: 콜백의 code
  • RedirectUri: 인가 URL의 redirect_uri에 넣은 값과 한 글자도 다르지 않은 값
  • CodeVerifier: 인가 URL에 code_challenge를 넣었다면 짝이 되는 codeVerifier. 넣지 않았다면 지정하지 않음
using Hive.Axyl.Auth;
using Hive.Axyl.Auth.Addon.WebAuth;
using Hive.Axyl.Core;

if (!HiveCore.TryResolve<IExternalUserAgent>(out var webAuth))
{
    return;
}

IAuthService auth = HiveCore.Resolve<IAuthService>();

// Google용 PKCE 값은 인가 URL을 만들기 전에 생성해 보관합니다. 이 예시는 PKCE를 사용합니다.
var (googleVerifier, googleChallenge) = CreatePkce();

// 로그인을 시도할 때마다 새로 만들어 보관합니다.
string state = CreateNonce();

// 인가 URL은 Google OAuth 2.0 규격에 맞춰 앱이 만듭니다.
string authorizationUrl = BuildGoogleAuthorizationUrl(googleChallenge, state, redirectUri);

var sessionResult = await webAuth.OpenAsync(new OpenRequest {
    Url         = authorizationUrl,
    RedirectUri = redirectUri,
});
if (sessionResult is not ExternalUserAgentServiceOpenResult.Success session)
{
    // UserCanceled와 Failure 처리는 [웹 로그인 세션](web-auth-session.md) 참조
    return;
}

var callback = session.Data.Parameters;

// state를 가장 먼저 확인합니다.
if (!callback.TryGetValue("state", out var returnedState) || returnedState != state)
{
    // 이번 로그인에서 시작한 응답이 아님 → 로그인 중단
    return;
}

if (callback.TryGetValue("error", out var googleError))
{
    // "access_denied"이면 사용자가 거절함 → 로그인 화면 유지
    // 그 밖의 값이면 로그인 실패 → 로그인 중단
    return;
}

if (!callback.TryGetValue("code", out var googleCode))
{
    // 인가 코드가 없음 → 로그인 중단
    return;
}

// 콜백에서 꺼낸 인가 코드를 로그인에 사용할 자격 증명으로 교환합니다.
var exchange = await auth.ExchangeProviderTokenAsync(new ProviderTokenRequest {
    ProviderId   = Provider.Google,
    ProviderCode = googleCode,
    RedirectUri  = redirectUri,      // 인가 URL의 redirect_uri와 같은 값
    CodeVerifier = googleVerifier,   // 인가 URL에 code_challenge를 넣었으므로 함께 보냅니다.
});
if (exchange is not AuthExchangeProviderTokenResult.Success exchanged)
{
    // 교환 실패 처리는 [외부 인증 코드 교환](provider-token-exchange.md) 참조
    return;
}

string googleProviderToken  = exchanged.Data.ProviderToken;    // 로그인 요청의 ProviderToken
string googleProviderUserId = exchanged.Data.ProviderUserId;   // 로그인 요청의 ProviderUserId

CreatePkce()는 게스트 계정 생성에서 정의한 헬퍼이고, CreateNonce()는 nonce 준비에서 정의한 난수 생성 헬퍼입니다. BuildGoogleAuthorizationUrl()은 앱이 직접 구현하는 인가 URL 생성 코드입니다.

2. 외부 인증 제공자 로그인

앞 단계에서 얻은 googleProviderUserId와 googleProviderToken으로 외부 인증 제공자 로그인을 호출합니다. ProviderId에는 Provider.Google을 지정합니다.

using Hive.Axyl.Auth;
using Hive.Axyl.Core;

IAuthService auth = HiveCore.Resolve<IAuthService>();

var (codeVerifier, codeChallenge) = CreatePkce();

var result = await auth.LoginProviderAsync(new ProviderLoginRequest {
    ProviderId          = Provider.Google,
    ProviderUserId      = googleProviderUserId,
    ProviderToken       = googleProviderToken,
    DeviceKey           = deviceKey,
    ClientId            = "{clientId}",
    CodeChallenge       = codeChallenge,
    CodeChallengeMethod = CodeChallengeMethod.S256,
});

if (result is AuthLoginProviderResult.Success success)
{
    // 로그인 성공 → 토큰을 발급하고 세션을 활성화합니다.
    await StartSessionAsync(success.Data.AuthorizationCode, codeVerifier, success.Data.PlayerId);
}
// 그 밖의 응답 케이스와 전체 호출 파라미터는 [외부 인증 제공자 로그인](provider-login.md) 참조
Note

여기에서 생성하는 PKCE 값은 Hive Axyl 인증 서버가 발급한 인가 코드를 토큰으로 교환할 때 사용합니다. Android 외 OS에서 Google의 인가 코드를 교환할 때 쓴 PKCE 값과는 별개이므로, 두 값을 섞어 쓰지 마세요.

StartSessionAsync()의 정의와 세션 활성화 절차는 토큰 발급과 세션 활성화를 참조하세요.

다음 단계