Skip to content
Stove
Last Updated

Are you curious about the actual implementation flow?

Usage Scenario / Logging In

Login (User Flow)

Understanding


Stove Login is the standard procedure for users to access games or web services using their Stove account. It supports various authentication methods, including email, guest, and 3rd party (Google, Facebook, Apple, etc.). Depending on the launch platform (Mobile/PC/Web), you can integrate it by using the SDK integrated UI or by configuring the UI directly within the game.

Login Flow

The login flow and tools used vary depending on the platform where the game is launched. You only need to integrate the necessary areas according to your game's launch format.

Platform Integration Tool Login UI Configuration Method Typical Entry Scenario
Mobile Mobile SDK Integrated UI (AuthUI) / Custom Configuration (Auth DATA API) App Launch → Account Login → Enter Game
PC PC SDK + Stove PC Client SDK automatically receives launcher login information Game launch after launcher login → Auto-login
Web Stove Web Authentication (GNB / Login URL) Responsive GNB UI or Stove login page Account login on official website/event page

Note on Platform Integration
ㆍ Multi-platform games integrate necessary platforms together, such as Mobile + PC + Web.
ㆍ Even if platforms differ, the Stove member identifier remains the same, ensuring a seamless account experience.

Login Process

Existing members log in, while new users sign up or enter the game as guests.
Regardless of the platform (Mobile/PC/Web), the login process can be summarized in the following 3 steps.

Step Task Description
1 Initialize SDK or web module Acquire initial information for using STOVE services
2 Perform login Log in with an account and issue a User Access Token. Handle terms of service agreement if necessary
3 Prepare to enter the game Save character information after selecting a server/world. Used for identification in coupons, pop-ups, billing, push notifications, etc.

Login Methods

The STOVE SDK supports two login methods. Although expressed based on the Mobile SDK, the same concepts are applied to each platform.

Method Description
Integrated Login UI (Auth.UI) Easily implement authentication with an integrated UI. Adding or excluding authentication methods is flexible.
Direct Login Implementation (DATA API) Implement desired authentication directly using the DATA API. Acquire tokens via login for each Provider.

Criteria for choosing a method
ㆍ If fast release and using a standard UI are priorities, we recommend the Integrated Login UI.
ㆍ If a unique game UI/UX is required, choose the direct login (DATA API) method.

Provider Types

A Provider is an authentication channel. Select and register the channels the game will expose. (Based on Mobile SDK)

Provider Android iOS Unity Unreal
Email (STOVE) O O O O
Guest O O O O
Google O O O O
Facebook O O O O
Apple O O O O
Steam O O
LINE O O O O
Naver O O O O
Twitter O O O O

PC/Web Channel Reference
ㆍ PC uses the login channel of the Stove PC client, and Web uses the channel of the Stove login page.
ㆍ PC/Web channels may vary depending on platform policies, so please check with the technical PM in charge.

Provider Display Order

On the mobile SDK login screen, providers are displayed in the order you have configured.

  • Email is mandatory and must be displayed first in the list.
  • Multi-platform (PC-supported) games must display all providers offered by Stove, and individual selection is not possible.
  • The recommended display order by region can be found in 이용 시나리오 > 로그인하기.

Additional Login Features

These are additional features provided by Stove Login in addition to the basic login.

Function Platform Description
GPG Login
(Google Play Games)
Mobile SDK (Android) 1:1 linking between Google Play Games account and STOVE account
Auto-login Mobile SDK, PC SDK Automatically logs in without input if a valid token exists

Precautions

Please check before linking login
Token validation on the server is mandatory after login. Please refer to 인증 가이드 for the validation flow.
Character setup is mandatory. If not set, back-office integration features (coupons, pop-ups, billing, push, etc.) will not work.

Integration Guide


Prerequisites by Platform

You only need to proceed with the prerequisites for the platforms required based on your game's release format.

Platform Required Items
Mobile ㆍ Apply Mobile SDK
ㆍ Set App ID/Client ID
ㆍ Register Package/Bundle ID
ㆍ Pre-configuration by 3rd party Provider
PC ㆍ Apply PC SDK (PCSDK3)
ㆍ Set App ID/Client ID
ㆍ Login is handled by STOVE PC client (no separate Provider setup required)
Web ㆍ Available if the domain is xxx.game.onstove.com
ㆍ Domain connection and referral path codes are handled by the Technical PM
ㆍ External infrastructure requires an SSL certificate
Server ㆍ Issue API Access Token for token validation (refer to Authentication Guide )
ㆍ Firewall settings required when using SSO

3rd party Provider pre-configuration must be completed before release
ㆍ Console settings, key issuance, and app settings for all Providers to be used must be completed to be displayed on the login screen.
ㆍ Failure to do so may delay the verification schedule.

Below is a summary of console settings, key issuance, and app settings for each 3rd party Provider. Please expand only the Provider items you intend to use. Follow Development Environment Setup for STOVE Maven repository registration and SDK module versions; this section only covers keys and settings for each Provider. Console key values (Client ID, Secret, Channel ID, etc.) are issued and provided by the publishing technical manager (Technical PM).

Googlegoogle_web_client_id · GoogleClientID · URL Scheme

Android requires google_web_client_id (includes client secret if supporting web login), and iOS requires GoogleClientID and a URL Scheme.

Key Issuance

  • Google Cloud Console → Obtain 'Web application' type Client ID (google_web_client_id) from Credentials. If supporting Google web login on Android, also obtain the client secret.
  • Obtain GoogleClientID and URL Scheme for iOS
  • Normal login is only possible if credentials are registered with your company's package name

Authorized Redirect URI (when calling web login)

Not mandatory unless the game calls web login.

CredentialsOAuth 2.0 Client IDsType 'Web application' → Register authorized redirect URIs for each environment.

text
Sandbox : https://m-member.gate8.com/google/redirect
Live    : https://m-member.onstove.com/google/redirect

Android

xml
<!-- strings.xml -->
<resources>
    <string name="google_web_client_id" translatable="false">{your-web-client-id}.apps.googleusercontent.com</string>
    <!-- 구글 웹 로그인 지원 시 -->
    <string name="google_web_client_secret" translatable="false">{your-web-client-secret}</string>
</resources>
xml
<!-- AndroidManifest.xml -->
<application>
    <meta-data
        android:name="com.stove.auth.google.web_client_id"
        android:value="@string/google_web_client_id" />

    <!-- 이하 구글 웹 로그인 지원 시에만 추가 -->
    <meta-data
        android:name="com.stove.auth.google.web_client_secret"
        android:value="@string/google_web_client_secret" />
    <activity
        android:name="com.stove.auth.google.CustomTabActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="stove-${applicationId}" />
        </intent-filter>
    </activity>
    <!-- Android 11+ 웹 로그인 지원 시 -->
    <queries>
        <intent>
            <action android:name="android.support.customtabs.action.CustomTabsService" />
        </intent>
    </queries>
</application>

iOS```xml

GoogleClientID {your-google-client-id} CFBundleURLTypes CFBundleURLSchemes {your-google-url-scheme} ```

!infoiOS Google SignIn framework version
ㆍ Google module 2.4.0 corresponds to Google SignIn v5.0.2, and 2.4.1 or higher corresponds to v6.2.2.
ㆍ The framework is external-frameworks 저장소Download the corresponding version from here.

Unity

  • StoveEdit Settings → Check 'Use Auth-Google' in the Inspector
  • (Android) WebClientIdEnter google_web_client_id (also enter the secret if web login is supported)
  • (iOS) ClientId·ClientSchemeEnter GoogleClientID·URL Scheme

Unreal

[StoveSDK] → [StoveSDK_APL.xml]Set the meta-data in .

xml
<!-- google login -->
<addElements tag="application">
    <meta-data
        android:name="com.stove.auth.google.web_client_id"
        android:value="{your-google-client-id}" />
    <!-- 웹 로그인 지원 시 -->
    <meta-data
        android:name="com.stove.auth.google.web_client_secret"
        android:value="{your-google-client-secret}" />
</addElements>

For iOS, merge the above Info.plist values into Project SettingsiOSExtra PList DataAdditional Plist Data.

Facebookfacebook_app_id · facebook_client_token

facebook_app_id and facebook_client_token of the Facebook app are required.

!infoclient_token is mandatory
ㆍ Setting the client_token is mandatory from Android auth-facebook 2.5.0 or higher and iOS AuthFacebook 2.4.1 or higher.

Key Issuance

  • Facebook for Developers → Select your app
  • facebook_app_id: Check in 'App ID' at the top left of the page
  • facebook_client_token: Check in SettingsAdvanced SettingsSecurityClient Token

Android

xml
<!-- strings.xml -->
<resources>
    <string name="facebook_app_id" translatable="false">{your-app-id}</string>
    <string name="fb_login_protocol_scheme" translatable="false">fb{your-app-id}</string>
    <string name="facebook_client_token" translatable="false">{your-client-token}</string>

</resources>
xml
<!-- AndroidManifest.xml -->
<application>
    <meta-data
        android:name="com.facebook.sdk.ApplicationId"
        android:value="@string/facebook_app_id" />
    <meta-data
        android:name="com.facebook.sdk.ClientToken"
        android:value="@string/facebook_client_token" />
    <activity
        android:name="com.facebook.CustomTabActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="@string/fb_login_protocol_scheme" />
        </intent-filter>
    </activity>
</application>

iOS

xml
<!-- Info.plist -->
<key>FacebookAppID</key>

<string>{your-app-id}</string>

<key>FacebookDisplayName</key>

<string>{your-app-name}</string>

<key>FacebookClientToken</key>

<string>{your-app-client-token}</string>

<key>CFBundleURLTypes</key>

<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>fb{your-app-id}</string>
    </array>
  </dict>
</array>
<key>LSApplicationQueriesSchemes</key>

<array>
    <string>fbapi</string>
    <string>fb-messenger-share-api</string>
    <string>fbauth2</string>
    <string>fbshareextension</string>

</array>

Unity

  • StoveEdit Settings → Check 'Use Auth-Facebook' in the Inspector
  • AppIdEnter facebook_app_id in

Unreal

[StoveSDK] → [StoveSDK_APL.xml]Set the meta-data in .

xml
<!-- facebook login -->
<addElements tag="application">
    <meta-data
        android:name="com.facebook.sdk.ApplicationId"
        android:value="fb{facebook_app_id}" />
    <meta-data
        android:name="com.facebook.sdk.ClientToken"
        android:value="facebook_client_token" />
</addElements>

For iOS, merge the above Info.plist values into Project SettingsiOSExtra PList DataAdditional Plist Data.

AppleServices ID · Capabilities

!infoSign In with Apple is mandatory for App Store release
ㆍ This is a 3rd party authentication that must be applied for iOS App Store release. It is supported from iOS 13 and later, and can be used on Android regardless of the version.

Android — Services ID Issuance and Settings

!warning.appleid suffix is mandatory for the identifier
ㆍ When creating a Service ID as a domain-style identifier, you must append .appleid as a suffix. (e.g., com.stove.mvp.google.appleid)

  • Register the Redirect URL for each environment.
text
m-member.gate8.com
m-member.onstove.com

https://m-member.gate8.com/appleid/redirect
https://m-member.onstove.com/appleid/redirect

!infoWhen using the same Service ID for multiple packages
ㆍ If you set com.stove.auth.apple.service.id, you can share the same Service ID across multiple packages. In this case, use the value excluding the suffix .appleid as the meta-data value. If not set, the app package name is used as the Service ID.

xml
<!-- strings.xml : .appleid 접미사를 제거한 값 사용 -->
<resources>
    <string name="apple_service_id" translatable="false">com.stove.mvp.google</string>

</resources>
xml
<!-- AndroidManifest.xml -->
<application>
    <meta-data
        android:name="com.stove.auth.apple.service.id"
        android:value="@string/apple_service_id" />
</application>

iOS — Capabilities Settings

  • Apple DeveloperAdd Sign In with Apple Capability to the target App ID in
  • Xcode → Add Sign In with Apple in Signing & Capabilities of the target
  • If additional data is needed after authentication, add Scopes before logging in.
objectivec
SGSAppleProvider *appleProvider = [[SGSAppleProvider alloc] init];
[appleProvider addScopes:@[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail]];

Unreal

For Android, set the meta-data in [StoveSDK] → [StoveSDK_APL.xml].

xml
<addElements tag="application">
    <meta-data
        android:name="com.stove.auth.apple.service.id"
        android:value="{your-apple-service-id}" />
</addElements>

For iOS, since the Capabilities item is not exposed in the settings menu, modify the config file directly. If you add Config/DefaultEngine.ini to the [/Script/IOSRuntimeSettings.IOSRuntimeSettings] item of bEnableSignInWithAppleSupport=True, Capabilities will be automatically entered during the build.

SteamNo key issuance required

!infoSteam does not require separate console/key issuance
ㆍ Since no separate developer console is used, key issuance/settings are unnecessary.
Auth / AuthUI 2.8.4 or higher is required.
ㆍ Steam Provider operates on Android/iOS native and is not supported on Unity/Unreal login screens.

Android

Just add the module dependency.

groovy
dependencies {
    // ...
    implementation 'com.stove:auth-steam:2.9.0'
}

iOS

Add the module to the Podfile.

ruby
target '{ProjectTargetName}' do
  # ...
  pod 'SGSAuthSteam', '2.9.0'
end
LINEline_channel_id

The line_channel_id of the LINE channel is required.

Key Issuance and Console Settings

  • LINE Developers → Check the Channel ID in your app
  • Register the Android packageName and iOS URL Scheme (Bundle ID) in the LINE Login tab

!warningPlease consult the Technical PM for LINE project registration
ㆍ Receive the Channel ID after LINE project registration.

Android

xml
<!-- strings.xml -->
<resources>
    <string name="line_channel_id" translatable="false">{your-channel-id}</string>

</resources>
xml
<!-- AndroidManifest.xml -->
<application>
    <meta-data
        android:name="com.stove.auth.line.channel_id"
        android:value="@string/line_channel_id" />
</application>

iOS

xml
<!-- Info.plist -->
<key>LINE_CHANNEL_ID</key>

<string>{your-line-channel-id}</string>

<key>CFBundleURLTypes</key>

<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    </array>
  </dict>
</array>
<key>LSApplicationQueriesSchemes</key>

<array>
  <string>lineauth2</string>

</array>

Unity

  • StoveEdit Settings → Check 'Use Auth-LINE' in the Inspector
  • channelId Enter the received Channel ID in

Unreal

[StoveSDK] → [StoveSDK_APL.xml] Set the meta-data in

xml
<!-- LINE login -->
<addElements tag="application">
    <meta-data
        android:name="com.stove.auth.line.channel_id"
        android:value="@string/line_channel_id" />
</addElements>

For iOS, merge the Info.plist values above into Project SettingsiOSExtra PList DataAdditional Plist Data.

Naverclient_id · client_secret · client_name

The naver_client_id·naver_client_secret·naver_client_name of the Naver app are required. naver_client_name is the app name displayed during login.

Key Issuance and Console Settings

  • Naver Developers → Check application information in your app
  • Register the Android app package name and iOS URL Scheme in API Settings

!warningPlease consult the Technical PM for Naver key issuance
ㆍ Receive the NaverClientID·NaverClientName·NaverClientSecret.

!infoiOS URL Scheme Format
ㆍ Enter the iOS Bundle ID in lowercase with the naver prefix. (Format: naver + iOS Bundle ID)
ㆍ E.g., If the Bundle ID is com.stove.mvp.iosqanavercom.stove.mvp.iosqa

Android

xml
<!-- strings.xml -->
<resources>
    <string name="naver_client_id" translatable="false">{your-client-id}</string>
    <string name="naver_client_secret" translatable="false">{your-client-secret}</string>
    <string name="naver_client_name" translatable="false">{your-app-name}</string>

</resources>
xml
<!-- AndroidManifest.xml -->
<application>
    <meta-data
        android:name="com.stove.auth.naver.client_id"
        android:value="@string/naver_client_id" />
    <meta-data
        android:name="com.stove.auth.naver.client_secret"
        android:value="@string/naver_client_secret" />
    <meta-data
        android:name="com.stove.auth.naver.client_name"
        android:value="@string/naver_client_name" />
</application>

iOS

xml
<!-- Info.plist : SGSAuthNaver v2.8.2 이상 -->
<key>NidClientID</key>

<string>{your-naver-client-id}</string>

<key>NidAppName</key>

<string>{your-naver-client-name}</string>

<key>NidClientSecret</key>

<string>{your-naver-client-secret}</string>

<key>NidUrlScheme</key>

<string>naver{iOS Bundle ID 소문자}</string>

<key>CFBundleURLTypes</key>

<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>naver{iOS Bundle ID 소문자}</string>
    </array>
  </dict>
</array>
<key>LSApplicationQueriesSchemes</key>

<array>
    <string>naversearchapp</string>
    <string>naversearchthirdlogin</string>

</array>

!infoKey Changes in v2.8.1 → v2.8.2 Update
NaverClientIDNidClientID, NaverClientNameNidAppName, NaverClientSecretNidClientSecret have been changed.
ㆍ The NidUrlScheme Key has been added. For the value, enter the Naver URL Scheme you entered in CFBundleURLTypes of CFBundleURLSchemes.
ㆍ Versions v2.8.1 and below use the existing NaverClientID series Keys.

Unity

  • StoveEdit Settings → Check 'Use Auth-Naver' in the Inspector
  • ClientId·ClientSecret·ClientName Enter the received values in

Unreal

[StoveSDK] → [StoveSDK_APL.xml] Set the meta-data in

xml
<!-- Naver login -->
<addElements tag="application">
    <meta-data
        android:name="com.stove.auth.naver.client_id"
        android:value="naver_client_id" />
    <meta-data
        android:name="com.stove.auth.naver.client_secret"
        android:value="naver_client_secret" />
    <meta-data
        android:name="com.stove.auth.naver.client_name"
        android:value="naver_client_name" />
</addElements>

For iOS, merge the Info.plist values above into Project SettingsiOSExtra PList DataAdditional Plist Data.

Overall Login Flow

The login sequence remains the same regardless of the platform (Mobile/PC/Web). Only the specific methods for implementing each step (SDK functions, UI configuration, etc.) vary by platform.

Development


This is the method for implementing authentication using the STOVE SDK's integrated login UI. The core flow of integrated login is Auth.initializesetProviderAuthUI.loginUser.setGameProfile It proceeds in a 4-step sequence.

1. Initialization (Auth.initialize)

This is the step to acquire the initial information required to use the service. Initialization must be completed before you can use subsequent SDK features such as login, payment, and coupons.

  • Acquire service configuration values(service_id, market_game_id)
  • Check if an app update is required
  • Check if the game is under maintenance

!infoApp updates and game maintenance are handled automatically
Auth.initialize If you pass the Result received in the response to OperationUI.handleResult(activity, result, listener) as is, the SDK will automatically display the maintenance(MaintenanceError 30003) / app update(AppUpdateError 30004) screen. Continue the game entry flow within the callback. Refer to the code example in the 앱 업데이트 및 게임 점검 처리 section below.

=== "Unity"

csharp
public void AuthInitialize()
{
    Auth.Initialize(result =>
    {
        if (result.IsSuccessful)
        {
            //초기화 성공
            /** 초기화 성공 및 앱 선택 업데이트 처리 **/
            OperationUI.HandleResult(result, (Result operationResult) =>
            {
                //IAP 초기화 | GameConfig 조회 | ...(중략)...
                /** ex) Touch To Screen 화면 노출 (=게임 시작이 준비되어 로그인 가능한 상태) **/
                //사용자가 화면을 터치
                if (Auth.AccessToken != null)
                {
                    /** AccessToken 있음 → 게스트 & 정회원 관계 없이 자동로그인으로 처리 **/
                    Login();
                }
                else
                {
                    /** AccessToken 없음 → 사용자 로그인 방식을 선택할 수 있도록 화면 제공 (`게스트 시작 & 로그인`) **/
                    //사용자가 `게스트 시작`을 선택한 경우 → `StartNewGuest()` 호출
                    //`로그인`을 선택한 경우 → `Login()` 호출
                }
            });

        }
        else if (result.Domain.Equals(Auth.Domain))
        {
            if (result.ErrorCode == AuthConfigurationError)
            {
                //설정 오류
                //set meta-data 'com.stove.environment' in AndroidManifest.xml
                //"check constants server :client_id and service_id are null or empty"
            }
            else
            {
                //ErrorCode == 30003 점검
                //ErrorCode == 30004 강제 업데이트
                /** 점검 및 앱 강제 업데이트 팝업 처리 **/
                OperationUI.HandleResult(result, (Result operationResult) =>
                {
                });
            }
        }
        else
        {
            //네트워크 또는 기타 서버 에러
            OperationUI.HandleResult(result, (Result operationResult) =>
            {
            });
        }
    });
}

=== "Unreal"

cpp
#include "Auth.h"
#include "OperationUI.h"
#include "Result.h"

Auth::Initialize([] (Result result) {
    string resultString = result.ToJSONString();

    if (result.IsSuccessful()) {
        OperationUI::HandleResult(result, [] (Result operationResult) {
            //IAP 초기화 | GameConfig 조회 | ...(중략)...
            /** ex) Touch To Screen 화면 노출 (=게임 시작이 준비되어 로그인 가능한 상태) **/
            //사용자가 화면을 터치
            AccessToken accessToken = Auth::GetAccessToken();

            if (!accessToken.IsNull()) {
            {
                /** AccessToken 있음 → 게스트 & 정회원 관계 없이 자동로그인으로 처리 **/
                Login();
            }
            else
            {
                /** AccessToken 없음 → 사용자 로그인 방식을 선택할 수 있도록 화면 제공 (`게스트 시작 & 로그인`) **/
                //사용자가 `게스트 시작`을 선택한 경우 → `StartNewGuest()` 호출
                //`로그인`을 선택한 경우 → `Login()` 호출
            }
        });
    } else if (result.domain.Equals(Auth::Domain, ESearchCase::CaseSensitive)) {
        if (result.errorCode == Auth::AuthConfigurationError) {

        } else {
            // 강제 업데이트, 점검
            OperationUI::HandleResult(result, [] (Result operationResult){

            });
        }
    } else {
        // 네트워크 또는 기타 서버 에러
        OperationUI::HandleResult(result, [] (Result operationResult){

        });
    }
});

=== "Android (Kotlin)"

kotlin
fun initialize(activity: Activity) {
    Auth.initialize(activity.applicationContext) { result ->
        when {
            result.isSuccessful() -> {
                //Init succeed. Handle optional app update
                OperationUI.handleResult(activity, result) {
                    //Do login.
                }
            }
            result.domain == Auth.Domain -> {
                when (result.errorCode) {
                    Auth.AuthConfigurationError -> {
                        //set meta-data 'com.stove.environment' in AndroidManifest.xml
                        //"check constants server :client_id and service_id are null or empty"
                    }
                    else -> {
                        //Handle required app update & maintenance
                        OperationUI.handleResult(activity, result) {

                        }
                    }
                }
            }
            else -> {
                //NetworkError
                //User notify & retry initialize
                OperationUI.handleResult(activity, result) {
                    initialize(activity)
                }
            }
        }
    }
}

=== "Android (Java)"

java
public void initialize(@NotNull final Activity activity) {
    Auth.initialize(this, (@NotNull Result result) -> {
        if (result.isSuccessful()) {
            //Init succeed. Handle optional app update
            OperationUI.handleResult(activity, result, (@NotNull Result handleResult) -> {
                //Do login.
                return null;
            });
        } else if (result.getDomain().equals(Auth.Domain)) {
            switch (result.getErrorCode()) {
                case Auth.AuthConfigurationError:
                    //set meta-data 'com.stove.environment' in AndroidManifest.xml
                    //"check constants server :client_id and service_id are null or empty"
                    break;
                default:
                    //Handle required app update & maintenance
                    OperationUI.handleResult(activity, result, (@NotNull Result handleResult) -> null);
            }
        } else {
            //NetworkError
            //User notify & retry initialize
            OperationUI.handleResult(activity, result, (@NotNull Result handleResult) -> {
                initialize(activity);
                return null;
            });
        }
        return null;
    });
}

=== "iOS"

objectivec
[SGSAuth initializeWithCompletionHandler:^(SGSResult * _Nonnull result) {
    if ([result isSuccessful]) {
        // 선택 업데이트 또는 성공
        [SGSOperationUI handleResult:result fromViewController:self completionHandler:^(SGSResult * _Nonnull result) {

        }];
    } else if ([[result domain] isEqualToString:SGSAuthErrorDomain]) {
        if ([result errorCode] == SGSAuthErrorAuthConfig) {
            // check constants server : client_id and service_id are null or empty
        } else {
            // 강제 업데이트, 점검
            [SGSOperationUI handleResult:result fromViewController:self completionHandler:^(SGSResult * _Nonnull result) {

            }];
        }
    } else {
        // 네트워크 또는 기타 서버 에러
        [SGSOperationUI handleResult:result fromViewController:self completionHandler:^(SGSResult * _Nonnull result) {

        }];
    }
}];

ErrorCodes

!infoCases with clear actions

  • 30001 AuthConfigurationError: The partner key (client_id·service_id) is missing. Please ensure the partner-issued key is correctly applied before calling initialization.
  • 30003 MaintenanceError / 30004 AppUpdateError: This is a normal response for maintenance or app updates. OperationUI.handleResultPass the result as-is to the SDK to display the automatic guidance UI. (Do not handle it manually)
DomainErrorCodeDescription
com.stove.success0Success
com.stove.auth30001AuthConfigurationError : Check constants server : client_id and service_id are null or empty
com.stove.auth30003MaintenanceError
com.stove.auth30004AppUpdateError
com.stove.base.network10001NoConnectionError
com.stove.base.network10002TimeoutError



App Update and Game Maintenance Handling

If the initialization result is 'App Update' or 'Game Maintenance', the UI is displayed to guide the user.


=== "Unity"

csharp
public void HandleResult(Result result)
{
    OperationUI.HandleResult(result, (Result handleResult) =>
    {

    });
}

=== "Unreal"

cpp
#include "OperationUI.h"
#include "Result.h"

OperationUI::HandleResult(result, [] (Result operationResult) {

});



=== "Android (Kotlin)"

kotlin
fun handleResult(activity: Activity, result : Result){
    OperationUI.handleResult(activity, result) {
    }
}

=== "Android (Java)"

java
OperationUI.handleResult(activity, result, (@NotNull Result it) -> {
    return null;
});

=== "iOS"

objectivec
[SGSOperationUI handleResult:result fromViewController:fromViewController completionHandler:^(SGSResult * _Nonnull result) {

}];



2. Provider Configuration (setProvider)

AuthUI.loginRegister the authentication channels (Providers) that the game will support before calling. They are displayed on the login screen in the order they were registered.


Adding Authentication Channels (Partner Pre-configuration)

Register authentication information in advance in the Partners Console for each Provider to be displayed on the login screen. Refer to the links below for each channel's configuration guide.

  • The Provider list is displayed on the login screen in the order they were added.
  • Email is a default setting on the login screen even if you do not add a Provider (however, the Email Provider is mandatory for login screen type B).
  • If the 'Email Provider' is not set on the connection screen, it will not be displayed.

Applying setProviders Code

AuthUI.setProvidersRegister the Providers the game will support. The order of the array passed is the order of display on the screen. AuthUI.login Must be executed before calling.


=== "Unity"

csharp
public void SetProviders()
{
    List<Provider> providers = new List<Provider>
    {
        new EmailProvider(),
        new StoveAppProvider(),
        new GuestProvider(),
        new GoogleProvider(),
        new FacebookProvider(),
        new AppleProvider(),
        new SteamProvider(),
        new LineProvider(),
        new NaverProvider(),
        new TwitterProvider()
    };
    AuthUI.Providers = providers;
}

=== "Unreal"

cpp
#include "EmailProvider.h"
#include "StoveAppProvider.h"
#include "GuestProvider.h"
#include "GoogleProvider.h"
#include "FacebookProvider.h"
#include "AppleProvider.h"
#include "SteamProvider.h"
#include "LineProvider.h"
#include "NaverProvider.h"
#include "TwitterProvider.h"
#include "AuthUI.h"

list<Provider*> provider;
provider.push_back(new EmailProvider());
provider.push_back(new StoveAppProvider());
provider.push_back(new GuestProvider());
provider.push_back(new GoogleProvider());
provider.push_back(new FacebookProvider());
provider.push_back(new AppleProvider());
provider.push_back(new SteamProvider());
provider.push_back(new LineProvider());
provider.push_back(new NaverProvider());
provider.push_back(new TwitterProvider());

AuthUI::SetProviders(provider);



=== "Android (Kotlin)"

kotlin
fun setProviders(context : Context) {
    AuthUI.setProviders(context, listOf(
        EmailProvider(), StoveAppProvider(), GuestProvider(),
        GoogleProvider(), FacebookProvider(), AppleProvider(),
        SteamProvider(), LineProvider(), NaverProvider(), TwitterProvider()
    ))
}

=== "Android (Java)"

java
public void setProviders(@NotNull Context context) {
    AuthUI.setProviders(context, CollectionsKt.listOf(
        new EmailProvider(), new StoveAppProvider(), new GuestProvider(),
        new GoogleProvider(), new FacebookProvider(), new AppleProvider(),
        new SteamProvider(), new LineProvider(), new NaverProvider(), new TwitterProvider()
    ));
}

=== "iOS"

objectivec
SGSEmailProvider *emailProvider = [[SGSEmailProvider alloc] init];
SGSStoveAppProvider *stoveappProvider = [[SGSStoveProvider alloc] init];
SGSGuestAppProvider *guestProvider = [[SGSGuestProvider alloc] init];
SGSAppleProvider *appleProvider = [[SGSAppleProvider alloc] init];
SGSGoogleProvider *googleProvider = [[SGSGoogleProvider alloc] init];
SGSFacebookProvider *facebookProvider = [[SGSFacebookProvider alloc] init];
SGSSteamProvider *steamProvider = [[SGSSteamProvider alloc] init];
SGSLineProvider *lineProvider = [[SGSLineProvider alloc] init];
SGSNaverProvider *naverProvider = [[SGSNaverProvider alloc] init];
SGSTwitterProvider *twitterProvider = [[SGSTwitterProvider alloc] init];

NSArray *providers = @[emailProvider, stoveappProvider, guestProvider,
                       appleProvider, facebookProvider, googleProvider,
                       steamProvider, lineProvider, naverProvider, twitterProvider];

[SGSAuthUI setProviders:providers];



3. Integrated Login (AuthUI.login)

!infoFlow automatically handled by the SDK within AuthUI.login
The following processes occur together with a single integrated login call. If you use the integrated UI, separate calls are not required.

FeatureAutomatic Display TimingCase requiring separate calls
Device Registration/ManagementAutomatically displayed when new device registration is requiredWhen launching the device management UI directly from the in-game settings screen
Identity VerificationAutomatically displayed when verification is required by law/policyWhen forcing re-verification before entering payment/sensitive features

For direct call codes, refer to the 기기 등록, 본인인증 documents.


AuthUI.loginWhen calling, it operates automatically according to the login flow.

It displays the login screen or refreshes the AccessToken, and if there are terms and conditions to show the user, it proceeds to handle them.

  • AuthUI.login Login UI display and AccessToken refresh are both handled with a single call.
  • 'Login with Stove APP' is only displayed on devices where the Stove APP is installed.

=== "Unity"

csharp
public void Login()
{
    AuthUI.Login((Result result, AccessToken accessToken) =>
    {
        if (result.IsSuccessful)
        {
            if (result.UserInfo != null && result.UserInfo.TryGetValue("userAction", out string userAction))
            {
                if (!string.IsNullOrEmpty(userAction) && userAction.Equals("sanction"))
                {
                    /** ex) 로그아웃 or 재시작 등 게임 시나리오에 맞게 적용 **/
                }
            }
        }
        else
        {
            if (result.IsServerError)
            {
                OperationUI.HandleResult(result, (Result operationResult) =>
                {
                    if (result.ErrorCode == 44008 || result.ErrorCode == 45006) { Auth.Logout(); }
                    if (result.ErrorCode == 44010) { Auth.Logout(); }
                });
            }
            else if(result.IsCanceled) {
                if (Auth.AccessToken != null) {
                    //자동 또는 수동 로그인 재시도
                    Login();
                }
                else {
                    Login();
                }
            }
            else
            {
                OperationUI.HandleResult(result, (Result operationResult) => { });
            }
        }
    });
}

=== "Unreal"

cpp
#include "AuthUI.h"
#include "AccessToken.h"
#include "OperationUI.h"
#include "Result.h"
#include "User.h"

AuthUI::Login([] (Result result, AccessToken accessToken) {
    if (result.IsSuccessful()) {
        if (!result.userInfo.IsEmpty()) {
            TMap<FString, FString> userInfoMap = result.userInfo;
            FString userAction = userInfoMap.FindRef(TEXT("userAction"));
            if (userAction == TEXT("sanction")) {
                //제재 처리
            }
        }
    } else {
        if (result.IsServerError()) {
            OperationUI::HandleResult(result, [] (Result operationResult){
                if (result.errorCode == 44008 || result.errorCode == 45006) { Auth::Logout(); }
                if (result.errorCode == 44010) { Auth::Logout(); }
            });
        } else if(result.IsCanceled()) {
            AccessToken accessToken = Auth::GetAccessToken();
            if (!accessToken.IsNull()) {
                //자동 또는 수동 로그인 재시도
            }
        } else {
            OperationUI::HandleResult(result, [] (Result operationResult) { });
        }
    }
});

=== "Android (Kotlin)"

kotlin
fun login(activity: Activity) {
    AuthUI.login(activity) { result, accessToken ->
        when {
            result.isSuccessful() -> {
                //제재된 사용자 확인 (=게임에서 직접 처리하는 경우)
                if(result.userInfo == AuthUI.Sanctioned) {
                    // 로그아웃 or 재시작 등 게임 시나리오에 맞게 적용
                    return@login
                }

                //로그인 이후 화면으로 이동
                val token = accessToken!!.token //AccessToken
                val user = accessToken.user
                val memberNumber: Long = user.memberNumber
                val verifiedIdentity: Boolean = user.verifiedIdentity
                val nationality: String = user.nationality
                val isGuest: Boolean = user.isGuest()
                val providers = user.providerUsers
                for (provider in providers) {
                    val type: Int = provider.type //1:StoveEmail, 2:Facebook, 3:Twitter, 6:Naver, 9:Google, 12:Apple, 13:Line, 15:Steam
                    val userId: String = provider.userId
                    val email: String? = provider.email
                    val verifiedEmail: Boolean? = provider.verifiedEmail
                }
            }
            result.isCanceled() -> {
                //사용자 취소, 로그인 화면 유지
            }
            result.isServerError() -> {
                OperationUI.handleResult(activity, result) { }
            }
            else -> {
                OperationUI.handleResult(activity, result) { }
            }
        }
    }
}

=== "Android (Java)"

java
public void login(@NotNull final Activity activity) {
    AuthUI.login(activity, (@NotNull Result result, @Nullable AccessToken accessToken) -> {
        if (result.isSuccessful()) {
            Map<String, String> userInfo = result.getUserInfo();
            if(userInfo != null && userInfo.containsKey("userAction")) {
                String userAction = userInfo.get("userAction");
                if(userAction != null && userAction.equals("sanction")) {
                    return null;
                }
            }
            String token = accessToken.getToken();
            User user = accessToken.getUser();
            long memberNumber = user.getMemberNumber();
            boolean verifiedIdentity = user.getVerifiedIdentity();
            String nationality = user.getNationality();
            boolean isGuest = user.isGuest();
            List<ProviderUser> providers = user.getProviderUsers();
            for (ProviderUser provider : providers) {
                int type = provider.getType();
                String userId = provider.getUserId();
                String email = provider.getEmail();
                boolean verifiedEmail = provider.getVerifiedEmail();
            }
        } else if (result.isCanceled()) {
            //사용자 취소
        } else if (result.isServerError()) {
            OperationUI.handleResult(activity, result, (@NotNull Result it) -> null);
        } else {
            OperationUI.handleResult(activity, result, (@NotNull Result it) -> null);
        }
        return null;
    });
}

=== "iOS"

objectivec
[SGSAuthUI loginWithFromViewController:nil completionHandler:^(SGSResult * _Nonnull result, SGSAccessToken * _Nullable accessToken) {
    if (result.isSuccessful) {
        NSString *userAction = [result.userInfo objectForKey:@"userAction"];
        if ([userAction isEqualToString:@"sanction"]) {
            return;
        }

        NSString *token = accessToken.token;
        SGSUser *user = accessToken.user;
        NSNumber *memberNumber = user.memberNumber;
        BOOL verifiedIdentity = user.verifiedIdentity;
        NSString *nationality = user.nationality;
        BOOL isGuest = user.isGuest;
        NSArray *providers = user.providerUsers;
        for (SGSProviderUser *providerUser in providers) {
            int type = providerUser.type;
            NSString *userId = providerUser.userId;
            NSString *email = providerUser.email;
            BOOL verifiedEmail = providerUser.verifiedEmail;
        }
    } else if (result.isCanceled) {
        //사용자 취소
    } else if (result.isServerError) {
        [SGSOperationUI handleResult:result fromViewController:self completionHandler:^(SGSResult * _Nonnull result) { }];
    } else {
        [SGSOperationUI handleResult:result fromViewController:self completionHandler:^(SGSResult * _Nonnull result) { }];
    }
}];

ErrorCodes

!infoCases with clear actions

  • 30001 AuthConfigurationError: client_id·service_idis null/empty. Check if the key issued by Partners is set correctly before calling SDK initialization.
  • 44010 Invalid refresh token: The stored refresh token is expired/invalid. Revoke the token and guide the user to the login screen to receive a new one.
  • 44009 Password has been changed: The existing token is invalid after a password change. Revoke the token and guide the user through the re-login flow.
  • 30302 ServerError (Device time change): This occurs when the user has arbitrarily changed the device time. Instruct the user to revert the time settings to automatic.
DomainErrorCodeDescription
com.stove.success0Success
com.stove.auth30001AuthConfigurationError : Check constants server : client_id and service_id are null or empty
com.stove.server43000ID or PW is incorrect.
com.stove.server43104Game restrict member
com.stove.server10125Error : It works only on normal devices. If this error persists, please contact Customer Service.
com.stove.server49500blocked IP address
com.stove.server41002Invalid game id
com.stove.server44001Withdrawal request member
com.stove.server44002Withdrawal member
com.stove.server44008Already Stove Account Link.
com.stove.server44010Invalid refresh token
com.stove.server41001Invalid client id
com.stove.server44000Sleep member
com.stove.server44009Password has been changed. Please login again.
com.stove.server30302ServerError — Occurs when the user plays after arbitrarily changing the device time
com.stove.base.network10001NoConnectionError
com.stove.base.network10002TimeoutError



4. Character Settings (setGameProfile)

After login is complete, enter the user's character and world information. The entered information is used by each feature (coupon/popup/billing/push) differentiated by world and character. Since the SDK does not separately validate the game profile, please be sure to perform a null check when entering it.

!dangerCharacter setting is mandatory
If you do not perform the character setting task, you cannot normally use the features (coupon/popup/billing/push) set through the Stove Backoffice.

Prerequisites:

  • To use Stove IAP/coupon services, World information must be pre-registered in Partners.
  • Even for games that do not support worlds, at least one world must be specified as default.

World-supported games

=== "Unity"

csharp
private void SetGameProfile()
{
    string CharacterNumber = "setYourCharacterNumber";
    string WorldId = "setYourWorld";
    AccessToken accessToken = Auth.AccessToken;
    if(accessToken == null) { return; }
    accessToken.User.GameProfile = new GameProfile(CharacterNumber, WorldId);
}

=== "Unreal"

cpp
#include "Auth.h"
#include "AccessToken.h"
#include "GameProfile.h"

FString world = "setYourWorld";
int64 chracternumber = "setYourCharacterNumber";

GameProfile gameProfile = GameProfile(chracternumber, world);

AccessToken accessToken = Auth::GetAccessToken();
if (!accessToken.IsNull()) {
    accessToken.user.SetGameProfile(gameProfile);
}

=== "Android (Kotlin)"

kotlin
private fun setGameProfile() {
    val characterNumber = "setYourCharacterNumber"
    val worldId = "setYourWorld"
    // characterNumber, world 값의 유효성 체크(null)를 꼭 해주세요.
    Auth.accessToken?.user?.gameProfile = GameProfile(characterNumber, worldId)
}

=== "Android (Java)"

java
private void setGameProfile(@NotNull Context context) {
    Long characterNumber = 111L; // setYourCharacterNumber
    String world = ""; // setYourCharacterWorld
    // characterNumber, world 값의 유효성 체크(null)를 꼭 해주세요.
    AccessToken accessToken = Auth.getAccessToken();
    if (accessToken != null) {
        User user = accessToken.getUser();
        user.setGameProfile(context, new GameProfile(characterNumber, world));
    }
}

=== "iOS"

objectivec
NSString *characterNumber = @"setYourCharacterNumber";
NSString *worldId = @"setYourWorld";
SGSAccessToken *accessToken = [SGSAuth accessToken];
if(accessToken == nil) { return; }
SGSGameProfile *gameProfile = [[SGSGameProfile alloc] initWithCharacterNumber:characterNumber world:worldId];
[[accessToken user] setGameProfile:gameProfile];

Games without world support

Even for games that do not support worlds, you only need to set characterNumber. world argument should be passed as null. However, at least one default world must be registered in Partners.


=== "Unity"

csharp
private void SetGameProfile()
{
    string CharacterNumber = "setYourCharacterNumber";
    AccessToken accessToken = Auth.AccessToken;
    if (accessToken == null) { return; }
    accessToken.User.GameProfile = new GameProfile(CharacterNumber);
}

=== "Unreal"

cpp
#include "Auth.h"
#include "AccessToken.h"
#include "GameProfile.h"

int64 chracternumber = "setYourCharacterNumber";

GameProfile gameProfile = GameProfile();
gameProfile.characterNumber = chracternumber;

AccessToken accessToken = Auth::GetAccessToken();
if ( accessToken != nullptr) {
    accessToken.user.SetGameProfile(gameProfile);
}



=== "Android (Kotlin)"

kotlin
private fun setGameProfile() {
    val characterNumber = "setYourCharacterNumber"
    // characterNumber 값의 유효성 체크(null)를 꼭 해주세요.
    Auth.accessToken?.user?.gameProfile = GameProfile(characterNumber, null)
}

=== "Android (Java)"

java
private void setGameProfile(@NotNull Context context) {
    Long characterNumber = 111L;
    AccessToken accessToken = Auth.getAccessToken();
    if (accessToken != null) {
        User user = accessToken.getUser();
        user.setGameProfile(context, new GameProfile(characterNumber, null));
    }
}

=== "iOS"

objectivec
NSString *characterNumber = @"setYourCharacterNumber";
SGSAccessToken *accessToken = [SGSAuth accessToken];
if(accessToken == nil) { return; }
SGSGameProfile *gameProfile = [[SGSGameProfile alloc] initWithCharacterNumber:characterNumber world:nil];
[[accessToken user] setGameProfile:gameProfile];

SDK Token Management

The SDK automatically renews the AccessToken when it reaches 80% of its expiration time. Since automatic renewal only works while the process is running, you should always query and use the AccessToken.

=== "Unity"

csharp
public void GetToken()
{
    string token = Auth.AccessToken.Token;
}

=== "Unreal"

cpp
#include "Auth.h"
#include "AccessToken.h"

AccessToken accessToken = Auth::GetAccessToken();

if (!accessToken.IsNull()) {
    FString token = accessToken.token;
}



=== "Android (Kotlin)"

kotlin
fun getAccessToken() {
    val token : String? = Auth.accessToken?.token
}

=== "Android (Java)"

java
public void getAccessToken() {
    AccessToken accessToken = Auth.getAccessToken();
}

=== "iOS"

objectivec
NSString *token = [[SGSAuth accessToken] token];

GPG Login Implementation

Used when implementing a 1:1 account link between a PGS ID and a Stove account for games preparing to launch Google Play Games Service (GPG). The SDK provides 3 APIs: Fetch (query linked account) / Load (load) / Link (link account).

!infoPGS ID ↔ Stove Account 1:1 Policy
After the initial link, if you log in with a different Stove account, it will operate by overriding the existing account information.
If a Stove account change occurs, such as converting to a full member, be sure to call GPG linking again.


Prerequisites

API Console / Partners Work

  • Apply for Console Permissions: Provide the package name (Android) for each service environment to the publishing technical manager to request it from Google Allowlist (takes 1-2 days)
  • Create Private Key: Issued in the Google Cloud Console. 생성 매뉴얼 PDF
  • Register Stove Partners: [파트너스] > [Launching] > [서비스 연동] > [모바일 마켓 정보] > [플레이스토어 마켓 앱] > [Private Key File 업로드]

SDK Module Application

  • play-services-games-v2 Library: Module officially released as of 2023/09/18. Manual EAP application is no longer required. Google 공식 다운로드
  • AUTH-GooglePlayGames Module: GPG connection module for the STOVE SDK. 최신 라이브 버전 Required for use

Project ID Settings (Android)

  1. Google Play ConsoleSelect your app in
  2. Play 게임서비스 > 설정 및 관리 > 설정Check the project ID in
  3. Apply to the files below

=== "build.gradle"

groovy
repositories {
    google()
    jcenter()
    maven {
        url "https://externalnexus.iam0.com/repository/mvp"
        // Android Gradle Plugin 7.0 이상 사용 시 아래 옵션 추가
        allowInsecureProtocol = true
    }
    mavenCentral()
}

dependencies {
    implementation 'com.stove:auth-googleplaygames:2.8.1'
}

=== "strings.xml"

xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="google_play_games_project_id" translatable="false">{your_project_id}</string>
</resources>

=== "AndroidManifest.xml"

xml
<manifest>
    <application>
        <meta-data
            android:name="com.google.android.gms.games.APP_ID"
            android:value="@string/google_play_games_project_id"/>
    </application>
</manifest>

Project ID Settings (Unity / Unreal)

  • Unity: Stove > Edit SettingsUse Auth-GooglePlayGames check → Projectid enter google_playgames_projectid in
  • Unreal: 프로젝트 셋팅 > Android > GooglePlayServices > 게임 앱 ID enter

Development Flow

  1. PGS SignIn and recallSessionID Acquisition (CP implementation area): After processing Google Play Games v2 SignIn, acquire the recallSessionID, which is the unique PGS identifier. Refer to Google 공식 가이드 for acquisition methods by language.
  2. SDK Initialization + Provider Settings: Call Auth.initialize, AuthUI.setProviders as in the existing integrated login flow.
  3. Auto-login Branching:
    • Auth.AccessToken exists → Proceed with existing auto-login flow (no separate GPG processing required)
    • Auth.AccessToken does not exist → GPGProvider(recallSessionID).fetch() look up STOVE account linked to
  4. Query Result Branching:
    • No linked account → Enter guest start or integrated login screen
    • Linked account exists + Game direct UI → GPGProvider(recallSessionID, account) create → Auth.Login(provider)
    • Linked account exists + SDK provided UI → GPGProvider.load()Auth.login(loadedProvider)
  5. Account Linking (Link): After login is complete, link the PGS ID and STOVE account with AccessToken.User.Link(GPGProvider) at an appropriate time (e.g., entering the lobby, returning to account settings). Must be re-called when converting to a full member.

Full Connection Sequence

This is a diagram showing the entire flow of PGS SignIn → acquiring recallSessionID → querying, logging in, and linking the connected account.

!info ※1 Linking follows a 1:1 policy, with a maximum of 1 linked account in the list
※2 Only applicable if the game directly configures an optimized UI for the user


Fetch — Query Linked Account

recallSessionID Query the list of STOVE accounts linked to the corresponding PGS ID from the STOVE server with


=== "Unity"

csharp
public void googlePlayGamesFetch(string sessionId)
{
    GPGProvider provider = new GPGProvider(sessionId); // recallSessionId
    provider.Fetch((Result result, List<Dictionary<string, object>> accounts) =>
    {
        if (result.IsSuccessful)
        {
            // accounts : 계정 목록
        }
    });
}

=== "Unreal"

cpp
#include "Auth.h"
#include "GPGProvider.h"
#include "Result.h"

GPGProvider* provider = new GPGProvider(googleGameSessionId);
provider->Fetch([] (Result result, list<map<string, string>> accounts) {
    if (result.IsSuccessful()) {
        // accounts : 계정 목록
    }
});

=== "Android (Kotlin)"

kotlin
private fun googlePlayGamesFetch(context: Context, sessionId: String) {
    val provider = GPGProvider(sessionId) // recallSessionId
    provider.fetch(context) { result, jsonArray ->
        if (result.isSuccessful()) {
            // jsonArray : 계정 목록
        }
    }
}

=== "Android (Java)"

java
public void googlePlayGamesFetch(@NotNull Context context, @NotNull String sessionId) {
    GPGProvider provider = new GPGProvider(sessionId); // recallSessionId
    provider.fetch(context, (result, jsonArray) -> {
        if (result.isSuccessful()) {
            // jsonArray : 계정 목록
        }
        return null;
    });
}

Load — Load Linked Account

Fetch After the user selects the account information received as a result in the SDK-provided UI, prepare the Provider so that they can auto-login with that account. Pass the returned loadedProvider to Auth.login.


=== "Unity"

csharp
public void googlePlayGamesLoad(string sessionId, Dictionary<string, object> account)
{
    /** account → Fetch의 결과로 얻은 계정 목록에서 획득 **/
    GPGProvider provider = new GPGProvider(sessionId); // recallSessionId
    provider.Load(account, (Result result, GPGProvider loadedProvider) =>
    {
        if (loadedProvider != null) {
            Auth.Login(loadedProvider, (Result loginResult, AccessToken accessToken) => {
                if (loginResult.IsSuccessful) {
                    // 로그인 성공
                } else if (loginResult.IsServerError) {
                    OperationUI.HandleResult(loginResult, (Result operationResult) =>
                    {
                        // 게스트 자동 로그인 불가 케이스
                        if (loginResult.ErrorCode == 44008 || loginResult.ErrorCode == 45006) {
                            Auth.Logout();
                        }
                    });
                }
            });
        }
    });
}

=== "Unreal"

cpp
#include "Auth.h"
#include "GPGProvider.h"
#include "Result.h"
#include "AccessToken.h"

GPGProvider* provider = new GPGProvider(googleGameSessionId);

provider->Load(list.front(), [] (Result result, GPGProvider* gpgProvider) {
    if (gpgProvider != nullptr) {
        Auth::Login(gpgProvider, [] (Result result, AccessToken* accessToken) {
            if (result.IsSuccessful()) {
                // 로그인 성공
            } else if (result.IsServerError()) {
                OperationUI::HandleResult(result, [] (Result operationResult) {
                    if (result.errorCode == 44008 || result.errorCode == 45006) {
                        Auth::Logout();
                    }
                });
            }
        });
    }
});

=== "Android (Kotlin)"

kotlin
private fun googlePlayGamesLoad(activity: Activity, sessionId: String, jsonObject: JSONObject) {
    /** jsonObject → Fetch의 결과로 얻은 계정 목록에서 획득 **/
    val provider = GPGProvider(sessionId) // recallSessionId
    provider.load(activity, jsonObject) { result, loadProvider ->
        loadProvider?.let {
            Auth.login(activity, it) { loginResult: Result, accessToken: AccessToken? ->
                if (loginResult.isSuccessful()) {
                    // 로그인 성공
                }
            }
        }
    }
}

=== "Android (Java)"

java
private void googlePlayGamesLoad(@NotNull Activity activity, @NotNull String sessionId, @NotNull JSONObject jsonObject) {
    /** jsonObject → Fetch의 결과로 얻은 계정 목록에서 획득 **/
    GPGProvider provider = new GPGProvider(sessionId); // recallSessionId
    provider.load(activity, jsonObject, (@NotNull Result result, @Nullable GPGProvider loadedProvider) -> {
        if (loadedProvider != null) {
            Auth.login(activity, loadedProvider, (@NotNull Result loginResult, @Nullable AccessToken accessToken) -> {
                if (loginResult.isSuccessful()) {
                    // 로그인 성공
                }
                return null;
            });
        }
        return null;
    });
}

Link — Linking Accounts

After logging in, link the PGS ID to the current STOVE account at an appropriate time. When a conversion to a full member occurs, you must call it again to maintain the link.


=== "Unity"

csharp
public void googlePlayGamesLink(string sessionId)
{
    GPGProvider provider = new GPGProvider(sessionId); // recallSessionId

    AccessToken accessToken = Auth.AccessToken;
    if (accessToken == null) { return; }
    accessToken.User.Link(provider, (Result result) =>
    {
        if (result.IsSuccessful)
        {
            // Google Play Games 연결 성공
        }
    });
}

=== "Unreal"

cpp
#include "Auth.h"
#include "GPGProvider.h"
#include "Result.h"
#include "AccessToken.h"

GPGProvider* provider = new GPGProvider(googleGameSessionId);

AccessToken *accessToken = Auth::GetAccessToken();
if (accessToken != nullptr) {
    accessToken->user.Link(provider, [] (Result result) {
        if (result.IsSuccessful()) {
            // Google Play Games 연결 성공
        }
    });
}

=== "Android (Kotlin)"

kotlin
private fun googlePlayGamesLink(activity: Activity, sessionId: String) {
    val provider = GPGProvider(sessionId) // recallSessionId
    Auth.accessToken?.user?.link(activity, provider) { result: Result ->
        if (result.isSuccessful()) {
            // Google Play Games 연결 성공
        }
    }
}

=== "Android (Java)"

java
private void googlePlayGamesLink(@NotNull Activity activity, @NotNull String sessionId) {
    GPGProvider provider = new GPGProvider(sessionId); // recallSessionId
    AccessToken accessToken = Auth.getAccessToken();
    if (accessToken != null) {
        User user = accessToken.getUser();
        if (user != null) {
            user.link(activity, provider, (@NotNull Result result) -> {
                if (result.isSuccessful()) {
                    // Google Play Games 연결 성공
                }
                return null;
            });
        }
    }
}

Troubleshooting

SituationCauseSolution
GPG link is disconnected after conversion to a full memberThe PGS ID ↔ STOVE account relationship follows a 1:1 policy. When a STOVE account is converted to a full member, the existing PGS link must also be updated to function correctly.Immediately after the conversion to a full member is complete, AccessToken.User.Link(GPGProvider(recallSessionID)) call it again.
ErrorCode 44008 occurs — when using the guest retention optionIf a conversion to a full member occurs on another device, the guest token backed up on the existing device expires.Delete the backed-up guest token and call Auth.logout to re-enter the login flow.
Fetch result returns an empty listIt may be a normal state where no STOVE account is linked to the PGS ID yet. (Allowlist Communication itself may fail if unapproved or if the Private Key is not registered)If it is a new user, naturally guide them to the guest start or integrated login screen. If communication failure is suspected, check the partner's Private Key registration and the console Allowlist approval status.

Google Console API Settings

This is how to register IAP (In-App Purchase) information for apps released on Google Play to STOVE Partner Billing. Since IAP authentication for STOVE SDK V2 uses Google's OAuth client method, Google Android Publisher API connection is required.

!warningPerform Google Play Console tasks with the owner (master) account.
If you proceed with another authorized account, access to some menus may be blocked, and the task may be interrupted.

The workflow consists of the following 4 steps.

  1. Connect Google Play Console API access permissions
  2. Create Google Cloud project and set up API/OAuth
  3. Obtain Refresh Token in OAuth 2.0 Playground
  4. Enter issuance information in STOVE Partner Billing settings

1. Obtain Google Play Console API Access Permissions

Connect to the Google Android Publisher API to verify IAP payments for apps registered on Google Play.

  1. Google Play Console > 설정 > API 액세스Go to .
  2. Select an existing Cloud project or connect a project created in advance at Google Cloud Console.
  3. Once the connection is successfully completed, the connected Google Cloud 프로젝트 and 프로젝트의 API 목록 will be displayed on the settings page.

If the connected project status does not appear on the screen, check the connection with Google Cloud first. If the problem persists after settings, refer to the official Google guide.


2. Create Google Cloud Project

!warningProceed only in the project connected to the actual service app (service project).
If you work in a test project, live billing verification will not work.


2-1) Create Project

  1. Access Google Cloud Console. The login account must be the owner of the Google Play Console developer account.
  2. Select IAM 및 관리자 > 프로젝트 만들기.
  3. Enter the project name and location to create the project.

2-2) Add API to use in project (Play Android Developer API)

  1. After selecting the created project, go to API 및 서비스 > 라이브러리.
  2. Search for and select Play Android Developer API, and set it to 사용 in the product details.

!warningIt must be activated in the same project as the one connected to the Play Console's API 액세스.
For IAP verification, Google Play Android Developer API must be enabled.


2-3) OAuth Consent Screen Setup and OAuth Client ID Creation

  1. API 및 서비스 Select the project to link in the dashboard.
  2. 사용자 인증 정보 In the menu, select 사용자 인증 정보 만들기 > OAuth 클라이언트 ID.
  1. Enter the basic information for the new OAuth client.
    • Application type: 웹 애플리케이션
    • Client name: A name that allows identification of IAP usage
    • Authorized JavaScript origins: Leave blank
    • Authorized redirect URIs: + URI 추가 Click and then enter https://developers.google.com/oauthplayground
  1. Once created, check and save the two values from the guide pop-up. (Used later for Partner input + Refresh Token issuance)
    • Client ID (OAuth Client ID)
    • Client Secret (OAuth Client Secret)

The values can also be found on the OAuth client information screen or in the downloaded client JSON.

For detailed OAuth setup methods, refer to the Google OAuth2 Guide.


3. Refresh Token Generation (OAuth 2.0 Playground)

  1. Go to Google OAuth 2.0 Playground.
  2. Click the gear button in the top right of the screen to open the OAuth 2.0 Configuration panel, and select the Use your own OAuth credentials checkbox.
  3. Enter the OAuth Client ID and OAuth Client Secret issued in the previous step, respectively.
  4. In the Step 1 area on the left, select Google Play Android Developer API or enter https://www.googleapis.com/auth/androidpublisher directly in the scope input field at the bottom.
  5. Click the Authorize APIs button at the bottom left.
  1. After authentication is complete and the screen switches to Step 2, click the Exchange authorization code for tokens button. You can check the Refresh token value in the response area.

4. Entering Information in Stove Partners Billing Settings

Enter the following 3 values issued in the steps above into IAP Information by Market in Stove Partners.

Frequently Asked Questions



Q1. Should I choose the integrated login UI or direct login (DATA API)?
A. We recommend the integrated login UI (Auth.UI) method for quick and easy authentication implementation.
If you need to configure the login screen directly in the game or selectively use specific Providers, you can use the direct login (DATA API) method.
Q2. How do I configure login for multi-platform (Mobile + PC + Web) games?
A. Integrate the login module separately for each platform.
Use the Mobile SDK for mobile, PC SDK + STOVE PC client for PC, and GNB or login URL method for web.
Since the Stove member identifier (member_no or guid) is the same regardless of the platform, account continuity is maintained, and the game server can process tokens from any environment using the same verification API.
Q3. When should I set up the character (GameProfile)?
A. You must complete the character setup after login is finished and before entering the game lobby.
If not configured, features set via the STOVE back office, such as coupons, pop-ups, billing, and push notifications, will not function properly.
Even when initializing the SDK or re-initializing it after cleanup,setGameProfileAPI must be called to reset the game's world and character information.
Q4. How should I manage the accessToken?
A. The SDK automatically refreshes the accessToken when it reaches 80% of its expiration time.
Since automatic renewal only works while the process is running, you must always retrieve and use the accessToken.
You must also verify the token's validity on the server side when requesting authentication from the server after logging in.
Q5. How do I convert a guest login to a full member account?
A. A guest account can be linked to a full member account by callingAuthUI.link(Integrated UI) orAuth.accessToken?.user?.link(DATA API).
It is recommended to call this only for guest accounts; if it is not a guest account, 'Link Channel' will be displayed.
Q6. How do I change the display order of Providers on the login screen?
A.AuthUI.setProviders()The providers are displayed on the login screen in the order of the list passed when calling.
The login screen may vary depending on the order in which the provider list is added, and the email option on the login screen is set by default even if you do not add a provider.
However, the email provider is mandatory for login screen type B.
Q7. How do I add Apple login on iOS?
A. Sign in with Apple is supported from iOS 13 and later, and it is a 3rd-party authentication that must be applied for App Store release.
You must create Services IDs in Apple Developer, set the Redirect URL, and add the Sign in with Apple Capability in Xcode.
For Unreal, if you addConfig/DefaultEngine.inito thebEnableSignInWithAppleSupport=Truefile, the capabilities will be set automatically.
Q8. How do I issue a Refresh Token for Google IAP integration?
A. You can issue it via the Google Developers OAuth 2.0 Playground.
In OAuth 2.0 Configuration, check "Use your own OAuth credentials," enter your OAuth Client ID and Client Secret, and
select the Play Android Developer API scope, then click Authorize APIs.
In Step 2, click the "Exchange authorization code for tokens" button to obtain the Refresh Token.
Enter the issued information in Stove Partners > Billing Settings.
Q9. What should I do if there is no account connected to GPG?
A. If there is no account connected to GPG, switch to the initial screen showing Guest Start or Integrated Login to proceed with manual login.
GPG login connection follows a 1:1 policy, and the maximum number of connected accounts is 1.
If you are configuring the screen directly in the game using the Data API, you need to handle branching based on whether the account is connected.
Q10. How long is the validity period of the SSO temporary key (state)?
A. The validity period of the temporary key (=state) is 10 minutes, and it can only be used once.
The temporary key becomes invalid immediately upon completion of the redirect on the web token exchange page (/auth/token-exchange) and cannot be reused.
You must issue a new temporary key via the temporary key issuance API whenever SSO integration is required.
Q11. Are there any restrictions on the redirect_url for SSO integration?
A. redirect_url is xxxx.onstove.com restricted only to pages that can be used with the domain.
The SSO target page must be confirmed by the publishing technical manager before integration.
Also, since this is communication between the game server and the Stove API server, infrastructure firewall settings must be completed in advance.



Need to contact us directly? stove.developers@smilegate.com