2단계. 로그인
사용자가 별도의 회원가입 없이 Steam 계정으로 앱에 로그인하도록 구현합니다. 시작하기 전에 1단계. 연동 환경 구성을 마치세요.
Steam 로그인은 앱이 실행되는 OS에 따라 자격 증명을 얻는 방법이 다릅니다. 두 방법 모두 Hive Axyl 인증 서버에서 바로 검증할 수 있는 값을 반환하므로 교환 단계 없이 Direct Token 흐름으로 로그인합니다.
- Windows, macOS: Steam 로그인 Add-on
- Android, iOS: 웹 로그인 세션
1. Steam 자격 증명 획득
앱이 실행되는 OS에 맞는 방법으로 Steam 자격 증명을 획득합니다.
1.1. Windows와 macOS
Windows와 macOS에서는 Steam 로그인 Add-on이 Steamworks에서 인증 티켓을 받아 옵니다. 사용자는 이미 Steam 클라이언트에 로그인한 상태이므로 별도의 로그인 화면이 나타나지 않습니다.
인증 티켓 요청
GetAuthTicketForWebApiAsync
ISteamPlugin.GetAuthTicketForWebApiAsync()를 호출해 인증 티켓을 받습니다. 티켓은 16진 문자열로 반환되며, 로그인 요청의 ProviderToken으로 사용합니다.
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Identity | string | Required | Steamworks가 티켓을 만들 때 사용하는 식별 문자열입니다. 빈 문자열을 넣으면 Steamworks의 기본 식별자를 사용합니다. Steamworks는 30자를 넘는 값을 거부합니다. |
using Hive.Axyl.Auth.Addon.Steam;
using Hive.Axyl.Core;
using UnityEngine;
// Add-on은 Windows와 macOS 빌드에서만 등록됩니다.
if (!HiveCore.TryResolve<ISteamPlugin>(out var steam))
{
// Windows나 macOS가 아니거나 Add-on이 등록되지 않음 → 웹 로그인 세션으로 분기
return;
}
var ticketResult = await steam.GetAuthTicketForWebApiAsync(
new GetAuthTicketForWebApiRequest { Identity = string.Empty });
string steamProviderToken; // 로그인 요청의 ProviderToken
switch (ticketResult)
{
case SteamServiceGetAuthTicketForWebApiResult.Success success:
steamProviderToken = success.Data.TicketHex;
break;
case SteamServiceGetAuthTicketForWebApiResult.NotAuthenticated:
// Steam 클라이언트에 로그인되어 있지 않음 → Steam 로그인 안내
return;
case SteamServiceGetAuthTicketForWebApiResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
return;
// 안전망: 처리하지 않은 결과 및 알 수 없는 신규 결과(UnknownOutcome)
default:
Debug.LogWarning($"처리되지 않은 결과: {ticketResult.GetType().Name}");
return;
}
// 현재 Steam 클라이언트에 로그인한 사용자의 Steam ID64입니다.
string steamProviderUserId = GetCurrentSteamId64();
GetCurrentSteamId64()는 Steamworks에서 현재 사용자의 Steam ID64를 읽어 오도록 앱이 직접 구현하는 코드입니다. Steam 로그인 Add-on은 이 값을 제공하지 않습니다.
Note
Hive Axyl 인증 서버는 인증 티켓을 Steam에 확인해 실제 Steam 계정을 확정합니다. 앱이 보낸 Steam ID64는 그 확정 과정을 대체하지 않습니다.
응답 상태
| 응답 케이스 | 설명 | 앱 클라이언트 대응 |
|---|---|---|
Success | 티켓 발급 성공. Data.TicketHex를 ProviderToken으로 사용합니다. | 외부 인증 제공자 로그인 진행 |
NotAuthenticated | Steam 클라이언트에 로그인되어 있지 않은 경우 | Steam 클라이언트 로그인 안내 |
UnknownOutcome | 이 SDK 버전이 알지 못하는 신규 결과 | 로깅 후 보수적으로 처리 |
Failure | 공통 Failure입니다. Steamworks를 초기화하지 않은 경우(FailedPrecondition)도 여기로 분기하며 원인은 Failure.Problem.Code에 담깁니다. 공통 오류 처리를 참조하세요. | 공통 오류 처리 기준에 따라 처리 |
인증 티켓 반납
ReleaseTicket
Steam은 앱이 동시에 보유할 수 있는 인증 티켓 개수를 제한합니다. 티켓을 반납하지 않고 계속 발급하면 이후 발급 요청이 실패하므로, 로그인 결과를 받은 뒤 ReleaseTicket()으로 티켓을 반납하세요.
반납 시점은 외부 인증 제공자 로그인이 성공했든 실패했든 응답을 받은 다음입니다. 검증이 끝나기 전에 반납하면 Steam이 티켓을 무효로 판단해 로그인이 실패합니다.
ReleaseTicket()은 Unity 메인 스레드에서 호출합니다. 이미 반납했거나 알 수 없는 티켓을 넘겨도 아무 동작도 하지 않습니다.
1.2. Windows와 macOS 외 OS
Android와 iOS에서는 웹 로그인 세션으로 Steam 로그인 페이지를 열고 Steam의 OpenID 로그인 결과를 받습니다. Steam은 로그인 결과를 앱 고유 스킴 주소로 돌려보내지 않으므로, 결과는 Hive Axyl 중계 주소를 거쳐 앱 콜백 주소로 돌아옵니다. 받은 결과는 교환 단계 없이 그대로 로그인에 사용합니다.
앱은 아래 순서로 Steam 자격 증명을 받습니다.
- 새 난수로
return_to주소를 만들어 보관합니다. return_to주소를 넣은 Steam 로그인 요청 URL을 만듭니다.- 앱 콜백 주소를
OpenRequest.RedirectUri에 넣어 웹 로그인 세션을 엽니다. - 콜백을 검증합니다.
- 콜백에서
ProviderUserId와ProviderToken에 넣을 값을 꺼냅니다.
return_to 주소 구성
return_to 주소는 Steam이 로그인 결과를 돌려보낼 주소입니다. Hive Axyl 중계 주소 뒤에 앱 콜백 주소와 난수를 쿼리 파라미터로 붙여 만듭니다.
relayTo: Hive Axyl 중계 주소가 결과를 전달할 앱 콜백 주소{appId}://oauth-callback을 URL 인코딩한 값s: 로그인을 시도할 때마다 새로 만드는 암호학적으로 안전한 난수. 예시 코드에서는 Apple로 로그인에서 정의한CreateNonce()로 생성
Steam은 return_to 주소를 로그인 결과에 그대로 담아 돌려주므로, 만든 주소를 보관해 두었다가 콜백 검증에서 비교하세요. 앱 콜백 주소는 중계 주소와 앱 콜백 주소에서 정한 값입니다.
로그인 요청 URL 구성
Steam OpenID 로그인 엔드포인트 https://steamcommunity.com/openid/login에 아래 파라미터를 붙여 로그인 요청 URL을 만듭니다. 각 값은 URL 인코딩해서 넣으세요.
openid.ns:http://specs.openid.net/auth/2.0openid.mode:checkid_setupopenid.return_to: return_to 주소 구성에서 만든 주소openid.realm:return_to주소의 origin인https://core-api.hiveaxyl.comopenid.identity:http://specs.openid.net/auth/2.0/identifier_selectopenid.claimed_id:http://specs.openid.net/auth/2.0/identifier_select
웹 로그인 세션 열기
만든 로그인 요청 URL을 웹 로그인 세션의 OpenAsync()로 엽니다. OpenRequest.RedirectUri에는 중계 주소가 아닌 앱 콜백 주소 {appId}://oauth-callback을 넣으세요.
콜백 검증
Hive Axyl 중계 주소는 Steam이 보낸 쿼리 문자열을 바꾸지 않고 앱 콜백 주소로 전달합니다. 사용자가 Steam 로그인을 거절해도 Steam은 창을 닫지 않고 openid.mode가 cancel인 콜백을 돌려줍니다.
따라서 콜백을 받으면 로그인 값을 꺼내기 전에 콜백 파라미터가 아래 조건을 모두 만족하는지 확인하세요. 하나라도 만족하지 않으면 로그인을 중단하세요.
openid.mode의 값이id_resopenid.return_to의 값이 보관한return_to주소와 완전히 일치openid.claimed_id의 값이https://steamcommunity.com/openid/id/{Steam ID64}형식
로그인 값 추출
검증을 통과한 콜백에서 로그인 요청에 넣을 두 값을 꺼냅니다. 웹 로그인 세션은 콜백을 해석하지 않으므로 값을 꺼내는 코드는 앱이 직접 구현합니다.
ProviderUserId:openid.claimed_id에서https://steamcommunity.com/openid/id/뒤에 오는 Steam ID64ProviderToken: 콜백 URL에서?뒤부터#앞까지의 쿼리 문자열 전체
ProviderToken에는 파싱한 파라미터를 다시 조립한 문자열이 아니라 Steam이 보낸 쿼리 문자열을 그대로 넣으세요. Hive Axyl 인증 서버는 이 값을 Steam에 보내 로그인 결과의 진위를 확인하므로, 파라미터 순서가 바뀌거나 다시 인코딩되면 서명 검증에 실패해 로그인이 거부됩니다. 아래 예시의 ExtractQueryString()은 콜백 URL에서 ? 뒤부터 # 앞까지의 문자열을 그대로 잘라 내도록 앱이 직접 구현하는 코드입니다.
using System;
using Hive.Axyl.Auth.Addon.WebAuth;
using Hive.Axyl.Core;
if (!HiveCore.TryResolve<IExternalUserAgent>(out var webAuth))
{
return;
}
const string RelayUrl = "https://core-api.hiveaxyl.com/auth/v1/provider/callback";
const string SteamIdPrefix = "https://steamcommunity.com/openid/id/";
const string IdentifierSelect = "http://specs.openid.net/auth/2.0/identifier_select";
string appCallback = "{appId}://oauth-callback";
// 로그인을 시도할 때마다 새 난수로 return_to 주소를 만들어 보관합니다.
string returnTo = RelayUrl
+ "?relayTo=" + Uri.EscapeDataString(appCallback)
+ "&s=" + Uri.EscapeDataString(CreateNonce());
string steamLoginUrl = "https://steamcommunity.com/openid/login"
+ "?openid.ns=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0")
+ "&openid.mode=checkid_setup"
+ "&openid.return_to=" + Uri.EscapeDataString(returnTo)
+ "&openid.realm=" + Uri.EscapeDataString("https://core-api.hiveaxyl.com")
+ "&openid.identity=" + Uri.EscapeDataString(IdentifierSelect)
+ "&openid.claimed_id=" + Uri.EscapeDataString(IdentifierSelect);
var sessionResult = await webAuth.OpenAsync(new OpenRequest {
Url = steamLoginUrl,
RedirectUri = appCallback, // 중계 주소가 아니라 앱 콜백 주소
});
if (sessionResult is not ExternalUserAgentServiceOpenResult.Success session)
{
// UserCanceled와 Failure 처리는 [웹 로그인 세션](web-auth-session.md) 참조
return;
}
var callback = session.Data.Parameters;
callback.TryGetValue("openid.mode", out string openIdMode);
if (openIdMode != "id_res")
{
// "cancel"이면 사용자가 Steam 로그인을 거절한 경우 → 로그인 화면 유지
return;
}
if (!callback.TryGetValue("openid.return_to", out string returnedTo) || returnedTo != returnTo)
{
// 이번 로그인에서 시작한 응답이 아님 → 로그인 중단
return;
}
if (!callback.TryGetValue("openid.claimed_id", out string claimedId)
|| !claimedId.StartsWith(SteamIdPrefix, StringComparison.Ordinal)
|| !ulong.TryParse(claimedId.Substring(SteamIdPrefix.Length), out _))
{
// Steam ID64 형식이 아님 → 로그인 중단
return;
}
string steamProviderUserId = claimedId.Substring(SteamIdPrefix.Length);
string steamProviderToken = ExtractQueryString(session.Data.CallbackUrl);
2. 외부 인증 제공자 로그인
앞 단계에서 얻은 steamProviderUserId와 steamProviderToken으로 외부 인증 제공자 로그인을 호출합니다. ProviderId에는 Provider.Steam을 지정합니다.
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.Steam,
ProviderUserId = steamProviderUserId,
ProviderToken = steamProviderToken,
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) 참조
Windows와 macOS에서는 이 호출의 결과를 받은 뒤 인증 티켓 반납을 수행하세요.
CreatePkce()는 게스트 계정 생성에서 정의한 헬퍼이고, StartSessionAsync()의 정의와 세션 활성화 절차는 토큰 발급과 세션 활성화를 참조하세요.