딥링크를 통한 인증 설정

딥 링크를 사용하면 사용자 ID 또는 엑솔라 로그인을 통한 인증 프로세스를 거치지 않아도 클릭 한 번으로 게임에서 웹샵으로 인증할 수 있습니다.

사용자의 모바일 기기에 게임이 설치되어 있으면 인증 토큰을 받을 수 있도록 딥 링크를 통해 게임으로 리디렉션됩니다.

사용자 절차

  1. 웹샵에서 인증되지 않은 사용자가 로그인 버튼 또는 구매 버튼을 클릭합니다. 사용자 ID를 입력하거나 모바일 게임을 통해 로그인할 수 있는 모달 창이 열립니다.
  1. 사용자가 게임에서 로그인 버튼을 클릭합니다.
  2. 사용자는 인증된 사용자로 웹샵으로 자동 리디렉션되는 게임으로 리디렉션됩니다.

  1. 웹샵에서 인증되지 않은 사용자가 로그인 버튼 또는 구매 버튼을 클릭합니다. 사용자 ID를 입력하거나 QR 코드를 사용하여 모바일 버전의 게임에 로그인할 수 있는 모달 창이 열립니다.

  1. 사용자가 모바일 기기로 QR 코드를 스캔하면 모바일 기기에서 웹샵이 열립니다.
  2. 사용자의 모바일 기기에서 모바일 버전의 게임이 열립니다.
  3. 사용자는 인증된 사용자로 모바일 기기의 웹샵으로 자동 리디렉션됩니다.
  1. 게임의 모바일 앱 설정에서 딥링크를 통해 게임을 열 수 있는 URL 스키마를 등록합니다.
    • 안드로이드 애플리케이션의 경우 AndroidManifest.xml 파일에서
    • iOS 애플리케이션의 경우 Info.plist 파일에서
URL 스키마를 등록한 후 사용자가 게임에서 웹샵으로 인증하면 지정된 주소에서 게임이 열립니다. 게임을 등록할 때 사용하는 URL 스키마의 예시: gamename://authorize, 여기서:
  • gamename - 사용자 인증 시 모바일 기기에서 열어야 하는 게임 이름.
  • authorize - gamename 게임이 열린 후 수행해야 하는 작업 이름의 예시. 애플리케이션 운영 체제의 작업과 일치하는 작업 이름을 사용합니다.
iOS 애플리케이션에서 URL 스키마를 등록하는 예시:
Copy
Full screen
Small screen
    <key>CFBundleURLTypes</key>
      <array>
        <dict>
          <key>CFBundleTypeRole</key>
          <string>Editor</string>
          <key>CFBundleURLSchemes</key>
          <array>
            <string>gamename</string>
          </array>
        </dict>
      </array>
    

    Android 애플리케이션에 URL 스키마를 등록하는 예시:

    Copy
    Full screen
    Small screen
      <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="gamename" android:host="authorize" />
      </intent-filter>
      

      1. 게임의 사용자 ID를 사용하여 JWT 형식의 인증 토큰 생성을 구현합니다.

      매개 변수유형설명
      string관리자 페이지에서 인증 방식의 ID입니다. 필수 항목.
      string웹샵에서 사용자 인증 웹훅을 수신할 때 사용하는 URL입니다. 이 URL은 사용자 ID를 통한 인증 설정 시 지정된 주소와 일치해야 합니다. 엑솔라는 웹훅에 대한 응답으로 웹샵 사용자를 인증하기 위해 200 HTTP 코드를 기대합니다. 엑솔라가 404 HTTP 코드가 포함된 응답을 받거나 응답을 받지 못하면 사용자가 인증되지 않습니다.

      "webhookUrl": "https://nowebhook.com" 값을 지정하여 딥 링크를 통해 인증할 때 웹훅을 비활성화할 수 있습니다. 이 경우 엑솔라는 사용자의 존재 여부를 확인하지 않으므로 user.id 매개 변수에서 게임 내에 존재하는 사용자 ID를 전달해야 합니다.
      필수 항목.
      string프로젝트 ID는 관리자 페이지에서 찾을 수 있으며, 브라우저 주소 표시줄의 프로젝트 이름 옆에 표시됩니다. URL의 형식: https:​//publisher.xsolla.com/merchant ID/Publisher Account section. 필수 항목.
      string게임의 사용자 ID. 필수 항목.
      string사용자 이름. 필수 항목.

      curl을 사용하여 사용자 토큰 생성 API 메소드를 호출하는 예시:

      Copy
      Full screen
      Small screen
      curl --location 'https://sb-user-id-service.xsolla.com/api/v1/user-id' \
      --header 'Content-Type: application/json' \
      --data '{
       "loginId": "000001aa-001a-0ab0-00001-01a01a01a01a",
       "webhookUrl": "https://nowebhook.com",
       "settings": {
         "projectId": 123456,
         "merchantId": 123456
       },
       "user": {
         "id": "123",
         "name": "a-user-name"
       }
      }'
      

      {token=“JWT_TOKEN”}과 같은 응답을 받게 됩니다.

      1. 인증 성공 알림이 있는 팝업 창을 추가합니다(선택 사항).
      2. 획득한 사용자 토큰을 사용하여 브라우저에서 웹샵 열기를 구현합니다.

      인증된 사용자를 위한 브라우저에서 웹샵을 열 때 사용하는 URL의 생성 예시:

      • 사용자 정의 도메인을 사용하는 경우 https:​//example.com/?token={token}
      • 엑솔라 도메인을 사용하는 경우 https:​//example.xsollasitebuilder.com/?token={token}
      여기서 {token}는 사용자의 인증 토큰입니다.아래 제공된 코드 예시를 사용하여 웹사이트 빌더에서 직접 설정할 수 있습니다.
      설정 지원은 계정 관리자에게 문의하거나 csm@xsolla.com으로 이메일을 보내 주세요.
      알림
      먼저 사용자 ID를 통한 인증을 설정합니다. 이는 딥 링크 인증을 사용할 수 없는 경우 사용자가 다른 인증 방식을 사용할 수 있도록 하기 위해 필요합니다. 예를 들어, 사용자의 모바일 기기에 게임이 설치되어 있지 않은 경우입니다.
      1. 프로젝트를 관리자 페이지에서 엽니다.
      2. 사이드 메뉴에서 웹사이트 빌더를 클릭합니다.
      3. 사용자 ID를 통한 인증으로 웹샵 사이트의 카드에서 구성을 클릭합니다.
      4. 사용자 정의 코드 블록을 사용하여 딥 링크 인증을 추가합니다.
      5. 자바스크립트 탭의 사용자 정의 코드 블록에서 모바일 및 데스크톱 버전에 대한 딥 링크 인증을 구현하는 아래의 사용자 정의 코드를 추가합니다.
      1. 코드에 있는 상수 값을 사용자의 값으로 교체합니다.
        • DEEPLINK_URL - 딥 링크를 통해 게임을 열 때 사용하는 URL 스키마.
        • QR_CODE - SVG 형식에서 게임을 열 때 사용하는 QR 코드. QR코드에는 source = pc 매개 변수가 있는 웹샵 링크가 포함되어 있어야 합니다.
        • deeplink__description - 모바일 애플리케이션의 게임 인증 버튼 아래에 표시되는 설명.
        • qr-code-auth__description - 데스크톱 버전의 게임에서 QR 코드 아래에 표시되는 설명.
      Copy
      Full screen
      Small screen
      {
          const DEEPLINK_URL = 'gamename://authorize';
          const QR_CODE = `YOUR_SVG_WITH_QR_CODE`;
          handleQRCodeRedirect();
      
      // Event handlers are set up for opening the authentication modal or for successful authentication.
      // If the event of opening the authentication modal occurs, the handler is triggered and a button or QR code is added to this window.
      // If the event of successful authentication occurs, the handler is triggered and the successful authentication modal is opened.
      
          window.addEventListener('custom-event:modal:render', updateLoginModal);
          window.addEventListener('custom-event:authorization:login', showLoginSuccessPopup);
          let getToken;
      
          const searchParams = new URLSearchParams(window.location.search);
          const tokenParam = searchParams.get('token');
          if (tokenParam) {
              showLoginSuccessPopup(tokenParam);
          }
      
          window.SB.subscribe(api => {
              getToken = api.getToken;
          });
      
      // Customization of the authentication modal window (adding a QR code for the desktop version of the game or a login button for the mobile version of the game).
      
          function updateLoginModal({detail}) {
              const {name, element} = detail;
              if (name !== 'user-id' || element.dataset.isQrAdded === 'true') return;
              const isMobile = checkMobileDevice();
              const ModalBody = element.querySelector('.ui-site-modal-window__body');
              const ModulInput = ModalBody.querySelector('.user-id-modal__input');
              element.classList.toggle('mobile-layout', isMobile);
              ModalBody.append(getTemplate(isMobile));
      
              element.dataset.isQrAdded = true;
          }
      
      // Function to display the successful authentication modal window.
      
        function showLoginSuccessPopup(t) {
          let username = 'Gamer';
          try {
            const token = getToken?.() || t;
            if (token) {
              const jwtData = parseJwt(token);
              const payload = JSON.parse(jwtData.payload);
              if (payload.userInfo.name) {
                username = payload.userInfo.name;
              } else if (jwtData.username) {
                username = jwtData.username;
              } else if (jwtData.server_custom_id) {
                username = jwtData.server_custom_id;
              }
            }
          } catch (e) {
            console.log('error', e);
          }
      
          const Modal = document.querySelector('.deeplink-auth');
          const ModalTitle = Modal.querySelector('.deeplink-auth__title');
      
          ModalTitle.textContent = ModalTitle.textContent.replace(/\{username}/g, username);
      
          Modal.classList.add('_visible');
          Modal.querySelector('.deeplink-auth__button').addEventListener('click', closeModal);
      
          function closeModal() {
            Modal.classList.remove('_visible');
          }
        }
      
      // Function that displays a QR code for the desktop version of the game or a login button for the mobile version of the game. Your_game should be replaced with the actual name of your game. To continue with the authentication, the game has to be installed on the user's phone.
      
        function getTemplate(isMobile) {
          const Deeplink = document.createElement('div');
      
          if (isMobile) {
            Deeplink.innerHTML = `
              <div class="deeplink__title"><span class="deeplink__title-text">or login via game</span></div>
              <a href="${DEEPLINK_URL}" class="deeplink__button" rel="noopener">Login via Mobile Game</a>
              <div class="deeplink__description">
                You need to have the Your_game installed on your phone in order to proceed with authorization and purchase
              </div>
            `;
      
            return Deeplink;
          }
      
          Deeplink.innerHTML = `
            <div class="separator">
              <span class="separator__text">or login via game</span>
            </div>
            <div class="qr-code-auth">
              <div class="qr-code-auth__image">${QR_CODE}</div>
              <span class="qr-code-auth__description">
              Scan the QR code with your phone and you will continue purchasing on your mobile device
              </span>
            </div>
          `;
      
          return Deeplink;
        }
      
        function checkMobileDevice() {
          return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
        }
      
      //  Function that decodes the JWT token and returns its payload as a JavaScript object.
      
        function parseJwt(token) {
          const base64Url = token.split('.')[1];
          const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
          const jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
          }).join(''));
      
          return JSON.parse(jsonPayload);
        }
      
      // Function to verify the vent occurred when the user scanned the QR code.
      
        function handleQRCodeRedirect() {
          const url = new URL(window.location.href);
          const source = url.searchParams.get('source');
          if (source === 'pc') {
            if (checkMobileDevice()) {
              const button = document.getElementById("deeplink-redirect");
              button.click();
            }
      
            setTimeout(function () {
              url.searchParams.delete('source');
              window.history.pushState({}, '', url);
            }, 2000);
          }
        }
      }
      
      1. HTML 탭의 사용자 정의 코드 블록에서 웹샵에서 인증 성공을 나타내는 모달 창에 대한 코드를 추가합니다. 자체 텍스트 값을 설정할 수 있습니다.
      Copy
      Full screen
      Small screen
      <div class="ui-site-modal-window deeplink-auth">
        <div class="deeplink-auth__body">
          <div class="deeplink-auth__image"></div>
          <div class="deeplink-auth__title">Hi, {username}!</div>
          <div class="deeplink-auth__description">You’ve successfully logged in to the Web Shop</div>
          <button class="deeplink-auth__button">Continue</button>
        </div>
      </div>
      
      <a href="gamename://authorize" id="deeplink-redirect" style="display: none;"></a>
      
      1. CSS 탭의 사용자 정의 코드 블록에서 새 인터페이스 구성 요소에 대한 스타일을 추가합니다. 권장 스타일은 아래와 같습니다. 인증 성공 시 표시되는 고유한 이미지의 URL을 지정합니다.
      Copy
      Full screen
      Small screen
      --deeplink-success-image-url: url("Successful_authorization_image_url");
      
      .html-v2:has(.deeplink-auth._visible) {
        z-index: var(--modalsZIndex);
      }
      
      .user-id-modal__content {
        padding: 0 16px !important;
      }
      
      @media (min-width: 768px) {
        .user-id-modal__content {
          max-width: 460px !important;
          padding: 0 3.8rem 4rem !important;
          overflow: auto;
          justify-content: center;
        }
      }
      
      .mobile-layout .user-id-modal__close {
        right: 8px;
        top: 16px;
      }
      
      .user-id-modal__logo {
        width: 109px;
        height: 38px;
        margin: 4rem auto 2rem;
      }
      
      .user-id-modal.mobile-layout .user-id-modal__logo {
        margin: 3rem auto 2.5rem;
      }
      
      .user-id-modal__wrapper {
        margin-top: 0;
      }
      
      .user-id-modal__title {
        margin-bottom: 3rem;
      }
      
      .user-id-modal__input-wrapper .ui-site-input {
        height: 41px;
      }
      
      .user-id-modal__input {
        margin-top: 0;
        border-radius: var(--border-radius-16);
      }
      
      .user-id-modal__button {
        height: auto;
        margin: 12px auto 0;
        padding: 1.25rem;
        border-radius: var(--border-radius-16);
      }
      
      .user-id-modal__button .simple-button__text {
        font-size: 1.8rem;
        font-weight: 500;
        line-height: 2.5rem;
        letter-spacing: 0.5px;
      }
      
      .user-id-modal .ui-site-instruction-cut__title .ui-site-description {
        margin-bottom: 3rem;
        font-size: 1.2em;
      }
      
      .user-id-modal .ui-site-instruction-cut__steps {
        overflow-y: visible;
      }
      
      .user-id-modal .ui-site-instruction-card:first-child {
        margin-top: 0;
      }
      
      .user-id-modal .ui-site-instruction-card:last-child {
        padding-bottom: 4rem;
      }
      
      .user-id-modal .separator {
        display: flex;
        margin-bottom: 3.8rem;
        align-items: center;
      }
      
      .user-id-modal .separator__text {
        padding: 0 1.5rem;
        font-family: var(--main-font);
        font-size: 1.8rem;
        font-weight: 500;
        line-height: 2.5rem;
        letter-spacing: 0.8px;
      }
      
      .user-id-modal .separator::before,
      .user-id-modal .separator::after {
        content: "";
        background-color: #797979;
        height: 1px;
        flex-grow: 1;
      }
      
      .user-id-modal .qr-code-auth__image {
        display: block;
        width: 160px;
        height: 160px;
        margin: 0 auto 20px;
      }
      
      .user-id-modal .qr-code-auth__image svg {
        display: block;
        width: 100%;
        height: 100%;
      }
      
      .user-id-modal .qr-code-auth__description {
        display: inline-block;
        font-family: var(--main-font);
        font-size: 1.5rem;
        font-weight: 400;
        line-height: 2.5rem;
        text-align: center;
        color: rgba(255, 255, 255, 0.7);
      }
      
      .deeplink__title {
        display: flex;
        margin-bottom: 3.8rem;
        align-items: center;
      }
      
      .deeplink__title-text {
        padding: 0 1.5rem;
        font-family: var(--main-font);
        font-size: 1.8rem;
        font-weight: 500;
        line-height: 2.5rem;
        letter-spacing: 0.8px;
      }
      
      .user-id-modal .deeplink__title::before,
      .user-id-modal .deeplink__title::after {
        content: "";
        background-color: #797979;
        height: 1px;
        flex-grow: 1;
      }
      
      .deeplink__button {
        display: block;
        margin-bottom: 2.2rem;
        padding: 1.3rem;
        font-family: var(--main-font);
        font-size: 1.8rem;
        font-weight: 500;
        line-height: 2.5rem;
        letter-spacing: 0.5px;
        text-align: center;
        color: var(--button-text-color);
        background: var(--accent-color);
        border-radius: var(--border-radius-16);
        text-decoration: none;
        transition: transform .2s cubic-bezier(.509, .001, .25, 1);
      }
      
      .deeplink__button:hover {
        transform: scale(.95);
      }
      
      .deeplink__description {
        margin-bottom: 2rem;
        font-family: var(--main-font);
        font-size: 1.6rem;
        line-height: 2.3rem;
        text-align: center;
        color: rgba(255, 255, 255, 0.7);
        letter-spacing: 0.5px;
      }
      
      .deeplink__cta-wrapper {
        display: flex;
        flex-direction: row;
        gap: 1.8rem;
        padding: 0 1.2rem;
      }
      
      .deeplink__cta-wrapper .ui-site-calltoaction__item {
        margin: 0;
        flex-grow: 2;
      }
      
      .deeplink__cta-wrapper .ui-site-calltoaction {
        width: 100%;
        min-width: auto;
        height: auto;
        min-height: auto;
        padding-bottom: 36%;
        background-size: contain;
        border-radius: 0;
      }
      
      .deeplink-auth {
        display: none;
      }
      
      .deeplink-auth._visible {
        display: flex;
      }
      
      .deeplink-auth__body {
        position: relative;
        width: 100%;
        max-width: 336px;
        padding: 16px;
        border-radius: 12px;
        background-color: var(--colors-core-background-secondary-rgb);
        border: var(--border-size) solid var(--border-color);
        font-family: var(--main-font);
        color: var(--colors-core-text-primary);
      }
      
      @media (min-width: 768px) {
        .deeplink-auth__body {
          max-width: 460px;
          padding: 24px 30px;
        }
      }
      
      .deeplink-auth__image {
        margin-bottom: 20px;
        padding-bottom: 49%;
        border-radius: 12px;
        background-image: var(--deeplink-success-image-url);
        background-repeat: no-repeat;
        background-position: center;
        background-size: cover;
      }
      
      @media (min-width: 768px) {
        .deeplink-auth__image {
          padding-bottom: 37%;
        }
      }
      
      .deeplink-auth__title {
        margin-bottom: 10px;
        font-size: 20px;
        font-weight: 500;
        line-height: 24px;
        text-align: center;
        letter-spacing: 0.7px;
      }
      
      @media (min-width: 768px) {
        .deeplink-auth__title {
          margin-bottom: 20px;
          font-size: 24px;
          line-height: 30px;
        }
      }
      
      .deeplink-auth__description {
        margin-bottom: 20px;
        font-size: 13px;
        font-weight: 400;
        line-height: 18px;
        text-align: center;
        letter-spacing: 0.5px;
        color: rgba(255, 255, 255, 0.7);
      }
      
      .deeplink-auth__button {
        display: block;
        width: 100%;
        padding: 1.3rem;
        font-family: var(--main-font);
        font-size: 1.8rem;
        font-weight: 500;
        line-height: 2.5rem;
        letter-spacing: 0.5px;
        text-align: center;
        color: var(--button-text-color);
        background: var(--accent-color);
        border-radius: var(--border-radius-16);
        text-decoration: none;
        transition: transform .2s cubic-bezier(.509, .001, .25, 1);
      }
      
      @media (min-width: 768px) {
        .deeplink-auth__button {
          padding: 12px;
          font-size: 16px;
          line-height: 24px;
        }
      }
      
      .deeplink-auth__button:hover {
        transform: scale(.95);
      }
      
      이 기사가 도움이 되었나요?
      감사합니다!
      개선해야 할 점이 있을까요? 메시지
      유감입니다
      이 기사가 도움이 안 된 이유를 설명해 주세요. 메시지
      의견을 보내 주셔서 감사드립니다!
      메시지를 검토한 후 사용자 경험 향상에 사용하겠습니다.
      이 페이지 평가
      이 페이지 평가
      개선해야 할 점이 있을까요?

      답하기 원하지 않습니다

      의견을 보내 주셔서 감사드립니다!
      마지막 업데이트: 2024년 4월 10일

      오자 또는 기타 텍스트 오류를 찾으셨나요? 텍스트를 선택하고 컨트롤+엔터를 누르세요.

      문제 보고
      콘텐츠를 항상 검토합니다. 여러분의 피드백은 콘텐츠를 개선에 도움이 됩니다.
      후속 조치를 위해 이메일을 제공해 주세요
      의견을 보내 주셔서 감사드립니다!