Google ID 서비스로 이전

개요

Google API를 호출하기 위한 사용자별 액세스 토큰을 획득하기 위해 Google에서는 다음과 같은 여러 JavaScript 라이브러리를 제공합니다.

이 가이드에서는 이러한 라이브러리에서 Google ID 서비스 라이브러리로 이전하는 방법을 설명합니다.

이 가이드를 따르면 다음을 수행할 수 있습니다.

  • 지원 중단된 플랫폼 라이브러리를 ID 서비스 라이브러리로 대체
  • API 클라이언트 라이브러리를 사용하는 경우 지원 중단된 gapi.auth2 모듈, 메서드, 객체를 삭제하고 이를 ID 서비스에 상응하는 항목으로 대체합니다.

ID 서비스 JavaScript 라이브러리에서 변경된 사항에 대한 설명은 개요사용자 승인 작동 방식을 참고하여 주요 용어와 개념을 검토하세요.

사용자 가입 및 로그인 인증을 찾고 있다면 Google 로그인에서 이전을 참고하세요.

승인 흐름 식별

사용자 승인 흐름에는 암시적 흐름과 승인 코드 흐름의 두 가지가 있습니다.

웹 앱을 검토하여 사용 중인 승인 흐름의 유형을 식별합니다.

웹 앱이 암시적 흐름을 사용하고 있음을 나타내는 표시:

웹 앱이 승인 코드 플로우를 사용하고 있음을 나타내는 표시:

  • 구현은 다음을 기반으로 합니다.

  • 앱은 사용자의 브라우저와 백엔드 플랫폼에서 모두 실행됩니다.

  • 백엔드 플랫폼에서 승인 코드 엔드포인트를 호스팅합니다.

  • 백엔드 플랫폼은 사용자가 없어도 사용자를 대신하여 Google API를 호출합니다(오프라인 모드라고도 함).

  • 새로고침 토큰은 백엔드 플랫폼에서 관리하고 저장합니다.

경우에 따라 코드베이스에서 두 흐름을 모두 지원할 수 있습니다.

승인 흐름 선택

이전을 시작하기 전에 기존 흐름을 계속 사용할지 아니면 다른 흐름을 채택할지 결정해야 합니다.

승인 흐름 선택을 검토하여 두 흐름 간의 주요 차이점과 장단점을 파악하세요.

대부분의 경우 사용자 보안 수준이 가장 높은 승인 코드 흐름이 권장됩니다. 이 흐름을 구현하면 플랫폼에서 사용자의 캘린더, 사진, 구독의 주목할 만한 변경사항을 알리는 업데이트 가져오기와 같은 새로운 오프라인 기능을 추가할 수도 있습니다.

선택기를 사용하여 승인 흐름을 선택합니다.

암시적 흐름

사용자가 있는 동안 브라우저 내에서 사용할 액세스 토큰을 획득합니다.

암시적 흐름 예에서는 ID 서비스로 이전하기 전과 후의 웹 앱을 보여줍니다.

승인 코드 흐름

Google에서 발급한 사용자별 승인 코드가 백엔드 플랫폼으로 전송되며, 여기에서 액세스 토큰 및 갱신 토큰으로 교환됩니다.

승인 코드 흐름 예에서는 ID 서비스로 이전하기 전후의 웹 앱을 보여줍니다.

이 가이드에서는 굵게 표시된 안내에 따라 기존 기능을 추가, 삭제, 업데이트 또는 바꿉니다.

브라우저 내 웹 앱 변경사항

이 섹션에서는 Google ID 서비스 JavaScript 라이브러리로 이전할 때 브라우저 내 웹 앱에 적용할 변경사항을 검토합니다.

영향을 받는 코드 식별 및 테스트

디버그 쿠키는 영향을 받는 코드를 찾고 지원 중단 후 동작을 테스트하는 데 도움이 될 수 있습니다.

크거나 복잡한 앱에서는 gapi.auth2 모듈 지원 중단으로 영향을 받는 모든 코드를 찾기가 어려울 수 있습니다. 곧 지원이 중단될 기능의 기존 사용을 콘솔에 로깅하려면 G_AUTH2_MIGRATION 쿠키 값을 informational로 설정하세요. 원하는 경우 콜론과 키 값을 추가하여 세션 스토리지에도 로깅합니다. 로그인 후 사용자 인증 정보를 수신하고 수집된 로그를 나중에 분석할 수 있도록 백엔드로 전송합니다. 예를 들어 informational:showauth2use는 출처와 URL을 showauth2use이라는 세션 스토리지 키에 저장합니다.

gapi.auth2 모듈이 더 이상 로드되지 않을 때 앱 동작을 확인하려면 G_AUTH2_MIGRATION 쿠키 값을 enforced로 설정합니다. 이를 통해 시행일 전에 지원 중단 후 동작을 테스트할 수 있습니다.

가능한 G_AUTH2_MIGRATION 쿠키 값은 다음과 같습니다.

  • enforced gapi.auth2 모듈을 로드하지 않습니다.
  • informational 지원 중단된 기능의 사용을 JS 콘솔에 로깅 선택적 키 이름이 설정된 경우 세션 스토리지에도 로깅합니다(informational:key-name).

사용자 영향을 최소화하려면 프로덕션 환경에서 사용하기 전에 개발 및 테스트 중에 이 쿠키를 로컬로 먼저 설정하는 것이 좋습니다.

라이브러리 및 모듈

gapi.auth2 모듈은 로그인용 사용자 인증과 승인용 암시적 흐름을 관리합니다. 지원 중단된 이 모듈과 객체 및 메서드를 Google ID 서비스 라이브러리로 대체하세요.

문서에 포함하여 웹 앱에 ID 서비스 라이브러리를 추가합니다.

<script src="https://accounts.google.com/gsi/client" async defer></script>

gapi.load('auth2', function)를 사용하여 auth2 모듈을 로드하는 인스턴스를 삭제합니다.

Google ID 서비스 라이브러리는 gapi.auth2 모듈의 사용을 대체합니다. JavaScript용 Google API 클라이언트 라이브러리에서 gapi.client 모듈을 계속 안전하게 사용하고 검색 문서에서 호출 가능한 JS 메서드를 자동으로 생성하고, 여러 API 호출을 일괄 처리하고, CORS 관리 기능을 활용할 수 있습니다.

쿠키

사용자 승인에는 쿠키를 사용하지 않아도 됩니다.

사용자 인증에서 쿠키를 사용하는 방식에 관한 자세한 내용은 Google 로그인에서 이전을 참고하고, 다른 Google 제품 및 서비스의 쿠키 사용에 관한 내용은 Google에서 쿠키를 사용하는 방식을 참고하세요.

사용자 인증 정보

Google ID 서비스는 사용자 인증과 승인을 두 개의 별도 작업으로 분리하며 사용자 사용자 인증 정보는 별개입니다. 사용자를 식별하는 데 사용되는 ID 토큰은 승인에 사용되는 액세스 토큰과 별도로 반환됩니다.

이러한 변경사항을 확인하려면 예시 사용자 인증 정보를 참고하세요.

암시적 흐름

승인 흐름에서 사용자 프로필 처리를 삭제하여 사용자 인증과 승인을 분리합니다.

다음 Google 로그인 JavaScript 클라이언트 참조삭제합니다.

메서드

  • GoogleUser.getBasicProfile()
  • GoogleUser.getId()

승인 코드 흐름

ID 서비스는 브라우저 내 사용자 인증 정보를 ID 토큰과 액세스 토큰으로 분리합니다. 이 변경사항은 백엔드 플랫폼에서 Google OAuth 2.0 엔드포인트로 직접 호출하거나 플랫폼의 보안 서버에서 실행되는 라이브러리(예: Google API Node.js 클라이언트)를 통해 획득한 사용자 인증 정보에는 적용되지 않습니다.

세션 상태

이전에는 Google 로그인을 통해 다음을 사용하여 사용자 로그인 상태를 관리할 수 있었습니다.

웹 앱의 로그인 상태와 사용자 세션을 관리할 책임은 개발자에게 있습니다.

다음 Google 로그인 JavaScript 클라이언트 참조삭제합니다.

객체:

  • gapi.auth2.SignInOptions

메서드

  • GoogleAuth.attachClickHandler()
  • GoogleAuth.isSignedIn()
  • GoogleAuth.isSignedIn.get()
  • GoogleAuth.isSignedIn.listen()
  • GoogleAuth.signIn()
  • GoogleAuth.signOut()
  • GoogleAuth.currentUser.get()
  • GoogleAuth.currentUser.listen()
  • GoogleUser.isSignedIn()

클라이언트 구성

암시적 또는 승인 코드 흐름의 토큰 클라이언트를 초기화하도록 웹 앱을 업데이트합니다.

다음 Google 로그인 JavaScript 클라이언트 참조삭제합니다.

객체:

  • gapi.auth2.ClientConfig
  • gapi.auth2.OfflineAccessOptions

메서드

  • gapi.auth2.getAuthInstance()
  • GoogleUser.grant()

암시적 흐름

토큰 클라이언트 초기화의 예에 따라 TokenClientConfig 객체와 initTokenClient() 호출을 추가하여 웹 앱을 구성합니다.

Google 로그인 JavaScript 클라이언트 참조Google ID 서비스바꿉니다.

객체:

  • TokenClientConfig에서 gapi.auth2.AuthorizeConfig

메서드

  • google.accounts.oauth2.initTokenClient()에서 gapi.auth2.init()

매개변수:

  • TokenClientConfig.login_hintgapi.auth2.AuthorizeConfig.login_hint를 호출합니다.
  • TokenClientConfig.hdgapi.auth2.GoogleUser.getHostedDomain() 호출

승인 코드 흐름

코드 클라이언트 초기화의 예에 따라 CodeClientConfig 객체와 initCodeClient() 호출을 추가하여 웹 앱을 구성합니다.

암시적 흐름에서 승인 코드 흐름으로 전환하는 경우:

Google 로그인 JavaScript 클라이언트 참조 삭제

객체:

  • gapi.auth2.AuthorizeConfig

메서드

  • gapi.auth2.init()

매개변수:

  • gapi.auth2.AuthorizeConfig.login_hint
  • gapi.auth2.GoogleUser.getHostedDomain()

토큰 요청

버튼 클릭과 같은 사용자 동작은 암시적 흐름을 통해 액세스 토큰이 사용자 브라우저에 직접 반환되거나 사용자별 승인 코드를 액세스 토큰 및 갱신 토큰으로 교환한 후 백엔드 플랫폼에 반환되는 요청을 생성합니다.

암시적 흐름

액세스 토큰은 사용자가 로그인되어 있고 Google과의 활성 세션이 있는 동안 브라우저에서 획득하고 사용할 수 있습니다. 암시적 모드의 경우 이전 요청이 있었더라도 액세스 토큰을 요청하려면 사용자 동작이 필요합니다.

Google 로그인 JavaScript 클라이언트 참조Google ID 서비스바꿉니다.

메서드

  • TokenClient.requestAccessToken()에서 gapi.auth2.authorize()
  • GoogleUser.reloadAuthResponse()(TokenClient.requestAccessToken() 제공)

requestAccessToken()를 호출하여 액세스 토큰을 요청하는 팝업 UX 흐름을 시작하거나 기존 토큰이 만료될 때 새 토큰을 가져오려면 링크 또는 버튼을 추가하세요.

코드베이스를 업데이트하여 다음을 수행합니다.

  • requestAccessToken()를 사용하여 OAuth 2.0 토큰 흐름을 트리거합니다.
  • requestAccessTokenOverridableTokenClientConfig을 사용하여 여러 범위에 대한 하나의 요청을 여러 개의 작은 요청으로 분리하여 증분 승인을 지원합니다.
  • 기존 토큰이 만료되거나 취소되면 새 토큰을 요청합니다.

여러 범위로 작업하려면 한 번에 모든 범위에 대한 액세스를 요청하는 대신 필요할 때만 범위에 대한 액세스를 요청하도록 코드베이스를 구조적으로 변경해야 할 수 있습니다. 이를 증분 승인이라고 합니다. 각 요청에는 가능한 한 적은 범위가 포함되어야 하며, 단일 범위가 포함되는 것이 가장 좋습니다. 점진적 승인을 위해 앱을 업데이트하는 방법에 관한 자세한 내용은 사용자 동의를 처리하는 방법을 참고하세요.

액세스 토큰이 만료되면 gapi.auth2 모듈이 웹 앱의 유효한 새 액세스 토큰을 자동으로 가져옵니다. 사용자 보안을 강화하기 위해 Google ID 서비스 라이브러리에서는 이 자동 토큰 새로고침 프로세스를 지원하지 않습니다. 만료된 액세스 토큰을 감지하고 새 토큰을 요청하도록 웹 앱을 업데이트해야 합니다. 자세한 내용은 토큰 처리 섹션을 참고하세요.

승인 코드 흐름

requestCode()를 호출하여 Google에서 승인 코드를 요청하는 링크 또는 버튼을 추가합니다. 예는 OAuth 2.0 코드 흐름 트리거를 참고하세요.

만료되거나 취소된 액세스 토큰에 응답하는 방법에 관한 자세한 내용은 토큰 처리 섹션을 참고하세요.

토큰 처리

만료되거나 취소된 액세스 토큰이 사용될 때 실패한 Google API 호출을 감지하고 새 유효한 액세스 토큰을 요청하는 오류 처리 추가

만료되거나 취소된 액세스 토큰이 사용되면 Google API에서 401 Unauthorized HTTP 상태 코드와 invalid_token 오류 메시지를 반환합니다. 예시는 잘못된 토큰 응답을 참고하세요.

만료된 토큰

액세스 토큰은 수명이 짧으며 몇 분 동안만 유효한 경우가 많습니다.

토큰 취소

Google 계정 소유자는 언제든지 이전에 부여한 동의를 철회할 수 있습니다. 이렇게 하면 기존 액세스 토큰과 갱신 토큰이 무효화됩니다. revoke()를 사용하거나 Google 계정을 통해 플랫폼에서 취소를 트리거할 수 있습니다.

Google 로그인 JavaScript 클라이언트 참조Google ID 서비스바꿉니다.

메서드

  • google.accounts.oauth2.revoke()에서 getAuthInstance().disconnect()
  • google.accounts.oauth2.revoke()에서 GoogleUser.disconnect()

사용자가 플랫폼에서 계정을 삭제하거나 앱과의 데이터 공유에 대한 동의를 철회하려는 경우 revoke를 호출합니다.

웹 앱 또는 백엔드 플랫폼에서 액세스 토큰을 요청하면 Google에서 사용자에게 동의 대화상자를 표시합니다. Google에서 사용자에게 표시하는 동의 대화상자의 예를 참고하세요.

앱에 액세스 토큰을 발급하기 전에 사용자 동의를 요청하고 결과를 기록하려면 기존의 활성 Google 세션이 필요합니다. 기존 세션이 아직 설정되지 않은 경우 사용자가 Google 계정에 로그인해야 할 수 있습니다.

사용자 로그인

사용자가 별도의 브라우저 탭에서 Google 계정에 로그인하거나 브라우저 또는 운영체제를 통해 기본적으로 로그인할 수 있습니다. 사용자가 앱을 처음 열 때 Google 계정과 브라우저 간에 활성 세션을 설정하려면 사이트에 Google로 로그인을 추가하는 것이 좋습니다. 이렇게 하면 다음과 같은 이점이 있습니다.

  • 사용자가 로그인해야 하는 횟수를 최소화합니다. 활성 세션이 아직 없는 경우 액세스 토큰을 요청하면 Google 계정 로그인 프로세스가 시작됩니다.
  • JWT ID 토큰 인증 정보 email 필드를 CodeClientConfig 또는 TokenClientConfig 객체의 login_hint 매개변수 값으로 직접 사용합니다. 이 방법은 플랫폼에서 사용자 계정 관리 시스템을 유지하지 않는 경우에 특히 유용합니다.
  • Google 계정을 플랫폼의 기존 로컬 사용자 계정과 조회하고 연결하여 플랫폼의 중복 계정을 최소화합니다.
  • 새 로컬 계정이 생성되면 가입 대화상자와 흐름을 사용자 인증 대화상자와 흐름에서 명확하게 분리하여 필요한 단계 수를 줄이고 이탈률을 개선할 수 있습니다.

로그인 후 액세스 토큰이 발급되기 전에 사용자는 요청된 범위에 대해 애플리케이션에 동의해야 합니다.

동의 후 액세스 토큰이 사용자가 승인하거나 거부한 범위 목록과 함께 반환됩니다.

세부 권한을 사용하면 사용자가 개별 범위를 승인하거나 거부할 수 있습니다. 여러 범위에 대한 액세스를 요청할 때 각 범위는 다른 범위와 독립적으로 부여되거나 거부됩니다. 사용자 선택에 따라 앱은 개별 범위에 종속된 기능과 기능을 선택적으로 사용 설정합니다.

암시적 흐름

Google 로그인 JavaScript 클라이언트 참조Google ID 서비스바꿉니다.

객체:

  • TokenClient.TokenResponse에서 gapi.auth2.AuthorizeResponse
  • TokenClient.TokenResponse에서 gapi.auth2.AuthResponse

메서드

  • GoogleUser.hasGrantedScopes()(google.accounts.oauth2.hasGrantedAllScopes() 제공)
  • GoogleUser.getGrantedScopes()(google.accounts.oauth2.hasGrantedAllScopes() 제공)

Google 로그인 JavaScript 클라이언트 참조 삭제:

메서드

  • GoogleUser.getAuthResponse()

세부 권한 예시에 따라 hasGrantedAllScopes()hasGrantedAnyScope()로 웹 앱을 업데이트합니다.

승인 코드 흐름

인증 코드 처리의 안내에 따라 백엔드 플랫폼에 승인 코드 엔드포인트를 업데이트하거나 추가합니다.

코드 모델 사용 가이드에 설명된 단계에 따라 요청을 검증하고 액세스 토큰과 새로고침 토큰을 획득하도록 플랫폼을 업데이트합니다.

점진적 승인사용자가 부여한 액세스 범위 검사 안내에 따라 사용자가 승인한 개별 범위를 기반으로 기능을 선택적으로 사용 설정 또는 중지하도록 플랫폼을 업데이트합니다.

암시적 흐름 예시

이전 방식

GAPI 클라이언트 라이브러리

사용자 동의를 위한 팝업 대화상자를 사용하여 브라우저에서 실행되는 자바스크립트용 Google API 클라이언트 라이브러리의 예

gapi.auth2 모듈은 gapi.client.init()에 의해 자동으로 로드되고 사용되므로 숨겨집니다.

<!DOCTYPE html>
  <html>
    <head>
      <script src="https://apis.google.com/js/api.js"></script>
      <script>
        function start() {
          gapi.client.init({
            'apiKey': 'YOUR_API_KEY',
            'clientId': 'YOUR_CLIENT_ID',
            'scope': 'https://www.googleapis.com/auth/cloud-translation',
            'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
          }).then(function() {
            // Execute an API request which is returned as a Promise.
            // The method name language.translations.list comes from the API discovery.
            return gapi.client.language.translations.list({
              q: 'hello world',
              source: 'en',
              target: 'de',
            });
          }).then(function(response) {
            console.log(response.result.data.translations[0].translatedText);
          }, function(reason) {
            console.log('Error: ' + reason.result.error.message);
          });
        };

        // Load the JavaScript client library and invoke start afterwards.
        gapi.load('client', start);
      </script>
    </head>
    <body>
      <div id="results"></div>
    </body>
  </html>

JS 클라이언트 라이브러리

사용자 동의를 위한 팝업 대화상자를 사용하여 브라우저에서 실행되는 클라이언트 측 웹 애플리케이션용 OAuth 2.0

gapi.auth2 모듈이 수동으로 로드됩니다.

<!DOCTYPE html>
<html><head></head><body>
<script>
  var GoogleAuth;
  var SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly';
  function handleClientLoad() {
    // Load the API's client and auth2 modules.
    // Call the initClient function after the modules load.
    gapi.load('client:auth2', initClient);
  }

  function initClient() {
    // In practice, your app can retrieve one or more discovery documents.
    var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';

    // Initialize the gapi.client object, which app uses to make API requests.
    // Get API key and client ID from Google Cloud console.
    // 'scope' field specifies space-delimited list of access scopes.
    gapi.client.init({
        'apiKey': 'YOUR_API_KEY',
        'clientId': 'YOUR_CLIENT_ID',
        'discoveryDocs': [discoveryUrl],
        'scope': SCOPE
    }).then(function () {
      GoogleAuth = gapi.auth2.getAuthInstance();

      // Listen for sign-in state changes.
      GoogleAuth.isSignedIn.listen(updateSigninStatus);

      // Handle initial sign-in state. (Determine if user is already signed in.)
      var user = GoogleAuth.currentUser.get();
      setSigninStatus();

      // Call handleAuthClick function when user clicks on
      //      "Sign In/Authorize" button.
      $('#sign-in-or-out-button').click(function() {
        handleAuthClick();
      });
      $('#revoke-access-button').click(function() {
        revokeAccess();
      });
    });
  }

  function handleAuthClick() {
    if (GoogleAuth.isSignedIn.get()) {
      // User is authorized and has clicked "Sign out" button.
      GoogleAuth.signOut();
    } else {
      // User is not signed in. Start Google auth flow.
      GoogleAuth.signIn();
    }
  }

  function revokeAccess() {
    GoogleAuth.disconnect();
  }

  function setSigninStatus() {
    var user = GoogleAuth.currentUser.get();
    var isAuthorized = user.hasGrantedScopes(SCOPE);
    if (isAuthorized) {
      $('#sign-in-or-out-button').html('Sign out');
      $('#revoke-access-button').css('display', 'inline-block');
      $('#auth-status').html('You are currently signed in and have granted ' +
          'access to this app.');
    } else {
      $('#sign-in-or-out-button').html('Sign In/Authorize');
      $('#revoke-access-button').css('display', 'none');
      $('#auth-status').html('You have not authorized this app or you are ' +
          'signed out.');
    }
  }

  function updateSigninStatus() {
    setSigninStatus();
  }
</script>

<button id="sign-in-or-out-button"
        style="margin-left: 25px">Sign In/Authorize</button>
<button id="revoke-access-button"
        style="display: none; margin-left: 25px">Revoke access</button>

<div id="auth-status" style="display: inline; padding-left: 25px"></div><hr>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
        onload="this.onload=function(){};handleClientLoad()"
        onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
</body></html>

OAuth 2.0 엔드포인트

사용자 동의를 위해 Google로 리디렉션하는 브라우저에서 실행되는 클라이언트 측 웹 애플리케이션용 OAuth 2.0

이 예에서는 사용자의 브라우저에서 Google의 OAuth 2.0 엔드포인트에 직접 호출하며 gapi.auth2 모듈이나 JavaScript 라이브러리를 사용하지 않습니다.

<!DOCTYPE html>
<html><head></head><body>
<script>
  var YOUR_CLIENT_ID = 'REPLACE_THIS_VALUE';
  var YOUR_REDIRECT_URI = 'REPLACE_THIS_VALUE';
  var fragmentString = location.hash.substring(1);

  // Parse query string to see if page request is coming from OAuth 2.0 server.
  var params = {};
  var regex = /([^&=]+)=([^&]*)/g, m;
  while (m = regex.exec(fragmentString)) {
    params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
  }
  if (Object.keys(params).length > 0) {
    localStorage.setItem('oauth2-test-params', JSON.stringify(params) );
    if (params['state'] && params['state'] == 'try_sample_request') {
      trySampleRequest();
    }
  }

  // If there's an access token, try an API request.
  // Otherwise, start OAuth 2.0 flow.
  function trySampleRequest() {
    var params = JSON.parse(localStorage.getItem('oauth2-test-params'));
    if (params && params['access_token']) {
      var xhr = new XMLHttpRequest();
      xhr.open('GET',
          'https://www.googleapis.com/drive/v3/about?fields=user&' +
          'access_token=' + params['access_token']);
      xhr.onreadystatechange = function (e) {
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log(xhr.response);
        } else if (xhr.readyState === 4 && xhr.status === 401) {
          // Token invalid, so prompt for user permission.
          oauth2SignIn();
        }
      };
      xhr.send(null);
    } else {
      oauth2SignIn();
    }
  }

  /*

    *   Create form to request access token from Google's OAuth 2.0 server.
 */
function oauth2SignIn() {
  // Google's OAuth 2.0 endpoint for requesting an access token
  var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';

    // Create element to open OAuth 2.0 endpoint in new window.
    var form = document.createElement('form');
    form.setAttribute('method', 'GET'); // Send as a GET request.
    form.setAttribute('action', oauth2Endpoint);

    // Parameters to pass to OAuth 2.0 endpoint.
    var params = {'client_id': YOUR_CLIENT_ID,
                  'redirect_uri': YOUR_REDIRECT_URI,
                  'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly',
                  'state': 'try_sample_request',
                  'include_granted_scopes': 'true',
                  'response_type': 'token'};

    // Add form parameters as hidden input values.
    for (var p in params) {
      var input = document.createElement('input');
      input.setAttribute('type', 'hidden');
      input.setAttribute('name', p);
      input.setAttribute('value', params[p]);
      form.appendChild(input);
    }

    // Add form to page and submit it to open the OAuth 2.0 endpoint.
    document.body.appendChild(form);
    form.submit();
  }
</script>

<button onclick="trySampleRequest();">Try sample request</button>
</body></html>

새로운 방식

GIS만 해당

이 예시에서는 사용자 동의를 위해 토큰 모델을 사용하는 Google ID 서비스 JavaScript 라이브러리와 팝업 대화상자만 보여줍니다. 클라이언트를 구성하고, 액세스 토큰을 요청하고 획득하고, Google API를 호출하는 데 필요한 최소 단계 수를 보여주기 위해 제공됩니다.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      var access_token;

      function initClient() {
        client = google.accounts.oauth2.initTokenClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly \
                  https://www.googleapis.com/auth/contacts.readonly',
          callback: (tokenResponse) => {
            access_token = tokenResponse.access_token;
          },
        });
      }
      function getToken() {
        client.requestAccessToken();
      }
      function revokeToken() {
        google.accounts.oauth2.revoke(access_token, () => {console.log('access token revoked')});
      }
      function loadCalendar() {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'https://www.googleapis.com/calendar/v3/calendars/primary/events');
        xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
        xhr.send();
      }
    </script>
    <h1>Google Identity Services Authorization Token model</h1>
    <button onclick="getToken();">Get access token</button><br><br>
    <button onclick="loadCalendar();">Load Calendar</button><br><br>
    <button onclick="revokeToken();">Revoke token</button>
  </body>
</html>

GAPI async/await

이 예에서는 토큰 모델을 사용하여 Google ID 서비스 라이브러리를 추가하고, gapi.auth2 모듈을 삭제하고, JavaScript용 Google API 클라이언트 라이브러리를 사용하여 API를 호출하는 방법을 보여줍니다.

프로미스, async, await는 라이브러리 로드 순서를 적용하고 승인 오류를 포착하고 재시도하는 데 사용됩니다. API 호출은 유효한 액세스 토큰이 제공된 후에만 이루어집니다.

사용자는 페이지가 처음 로드될 때 액세스 토큰이 누락되거나 액세스 토큰이 만료된 후 '일정 표시' 버튼을 눌러야 합니다.

<!DOCTYPE html>
<html>
<head>
    <title>GAPI and GIS Example</title>
    <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoad()"></script>
    <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoad()"></script>
</head>
<body>
    <h1>GAPI Client with GIS Authorization</h1>
    <button id="authorizeBtn" style="visibility:hidden;">Authorize and Load Events</button>
    <button id="revokeBtn" style="visibility:hidden;">Revoke Access</button>
    <div id="content"></div>

    <script>
        const YOUR_CLIENT_ID = "YOUR_CLIENT_ID";
        const YOUR_API_KEY = 'YOUR_API_KEY';
        const CALENDAR_SCOPE = 'https://www.googleapis.com/auth/calendar.readonly';

        let tokenClient;
        let libsLoaded = 0;

        function gapiLoad() {
            gapi.load('client', initGapiClient);
        }

        async function initGapiClient() {
            try {
                await gapi.client.init({ apiKey: YOUR_API_KEY });
                await gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
                console.log('GAPI client initialized.');
                checkAllLoaded();
            } catch (err) {
                handleError('GAPI initialization failed:', err);
            }
        }

        function gisLoad() {
            try {
                tokenClient = google.accounts.oauth2.initTokenClient({
                    client_id: YOUR_CLIENT_ID,
                    scope: CALENDAR_SCOPE,
                    callback: '', // Will be set dynamically
                    error_callback: handleGisError,
                });
                console.log('GIS TokenClient initialized.');
                checkAllLoaded();
            } catch (err) {
                handleError('GIS initialization failed:', err);
            }
        }

        function checkAllLoaded() {
            libsLoaded++;
            if (libsLoaded === 2) {
                document.getElementById('authorizeBtn').style.visibility = 'visible';
                document.getElementById('revokeBtn').style.visibility = 'visible';
                document.getElementById('authorizeBtn').onclick = makeApiCall;
                document.getElementById('revokeBtn').onclick = revokeAccess;
                console.log('Ready to authorize.');
            }
        }

        function handleGisError(err) {
            console.error('GIS Error:', err);
            let message = 'An error occurred during authorization.';
            if (err && err.type === 'popup_failed_to_open') {
                message = 'Failed to open popup. Please disable popup blockers.';
            } else if (err && err.type === 'popup_closed') {
                message = 'Authorization popup was closed.';
            }
            document.getElementById('content').textContent = message;
        }

        function handleError(message, error) {
            console.error(message, error);
            document.getElementById('content').textContent = `${message} ${error.message || JSON.stringify(error)}`;
        }

        async function makeApiCall() {
            document.getElementById('content').textContent = 'Processing...';
            try {
                let token = gapi.client.getToken();
                if (!token || !token.access_token) {
                    console.log('No token, fetching one...');
                    await getToken();
                }

                console.log('Calling Calendar API...');
                const response = await gapi.client.calendar.events.list({ 'calendarId': 'primary' });
                displayEvents(response.result);
            } catch (err) {
                console.error('API call failed:', err);
                const errorInfo = err.result && err.result.error;
                if (errorInfo && (errorInfo.code === 401 || (errorInfo.code === 403 && errorInfo.status === "PERMISSION_DENIED"))) {
                    console.log('Auth error on API call, refreshing token...');
                    try {
                        await getToken({ prompt: 'consent' }); // Force refresh
                        const retryResponse = await gapi.client.calendar.events.list({ 'calendarId': 'primary' });
                        displayEvents(retryResponse.result);
                    } catch (refreshErr) {
                        handleError('Failed to refresh token or retry API call:', refreshErr);
                    }
                } else {
                    handleError('Error loading events:', err.result ? err.result.error : err);
                }
            }
        }

        async function getToken(options = { prompt: '' }) {
            return new Promise((resolve, reject) => {
                if (!tokenClient) return reject(new Error("GIS TokenClient not initialized."));
                tokenClient.callback = (tokenResponse) => {
                    if (tokenResponse.error) {
                        reject(new Error(`Token Error: ${tokenResponse.error} - ${tokenResponse.error_description}`));
                    } else {
                        console.log('Token acquired.');
                        resolve(tokenResponse);
                    }
                };
                tokenClient.requestAccessToken(options);
            });
        }

        function displayEvents(result) {
            const events = result.items;
            if (events && events.length > 0) {
                let eventList = '<h3>Upcoming Events:</h3><ul>' + events.map(event =>
                    `<li>${event.summary} (${event.start.dateTime || event.start.date})</li>`
                ).join('') + '</ul>';
                document.getElementById('content').innerHTML = eventList;
            } else {
                document.getElementById('content').textContent = 'No upcoming events found.';
            }
        }

        function revokeAccess() {
            const token = gapi.client.getToken();
            if (token && token.access_token) {
                google.accounts.oauth2.revoke(token.access_token, () => {
                    console.log('Access revoked.');
                    document.getElementById('content').textContent = 'Access has been revoked.';
                    gapi.client.setToken(null);
                });
            } else {
                document.getElementById('content').textContent = 'No token to revoke.';
            }
        }
    </script>
</body>
</html>

GAPI 콜백

이 예에서는 토큰 모델을 사용하여 Google ID 서비스 라이브러리를 추가하고, gapi.auth2 모듈을 삭제하고, JavaScript용 Google API 클라이언트 라이브러리를 사용하여 API를 호출하는 방법을 보여줍니다.

변수는 라이브러리 로드 순서를 적용하는 데 사용됩니다. GAPI 호출은 유효한 액세스 토큰이 반환된 후 콜백 내에서 이루어집니다.

사용자는 페이지가 처음 로드될 때와 캘린더 정보를 새로고침할 때 '캘린더 표시' 버튼을 눌러야 합니다.

<!DOCTYPE html>
<html>
<head>
  <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoad()"></script>
  <script async defer src="https://accounts.google.com/gsi/client" onload="gisInit()"></script>
</head>
<body>
  <h1>GAPI with GIS callbacks</h1>
  <button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br>
  <button id="revokeBtn" onclick="revokeToken();">Revoke access token</button>
  <script>
    let tokenClient;
    let gapiInited;
    let gisInited;

    document.getElementById("showEventsBtn").style.visibility="hidden";
    document.getElementById("revokeBtn").style.visibility="hidden";

    function checkBeforeStart() {
       if (gapiInited && gisInited){
          // Start only when both gapi and gis are initialized.
          document.getElementById("showEventsBtn").style.visibility="visible";
          document.getElementById("revokeBtn").style.visibility="visible";
       }
    }

    function gapiInit() {
      gapi.client.init({
        // NOTE: OAuth2 'scope' and 'client_id' parameters have moved to initTokenClient().
      })
      .then(function() {  // Load the Calendar API discovery document.
        gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
        gapiInited = true;
        checkBeforeStart();
      });
    }

    function gapiLoad() {
        gapi.load('client', gapiInit)
    }

    function gisInit() {
     tokenClient = google.accounts.oauth2.initTokenClient({
                client_id: 'YOUR_CLIENT_ID',
                scope: 'https://www.googleapis.com/auth/calendar.readonly',
                callback: '',  // defined at request time
            });
      gisInited = true;
      checkBeforeStart();
    }

    function showEvents() {

      tokenClient.callback = (resp) => {
        if (resp.error !== undefined) {
          throw(resp);
        }
        // GIS has automatically updated gapi.client with the newly issued access token.
        console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));

        gapi.client.calendar.events.list({ 'calendarId': 'primary' })
        .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
        .catch(err => console.log(err));

        document.getElementById("showEventsBtn").innerText = "Refresh Calendar";
      }

      // Conditionally ask users to select the Google Account they'd like to use,
      // and explicitly obtain their consent to fetch their Calendar.
      // NOTE: To request an access token a user gesture is necessary.
      if (gapi.client.getToken() === null) {
        // Prompt the user to select a Google Account and asked for consent to share their data
        // when establishing a new session.
        tokenClient.requestAccessToken({prompt: 'consent'});
      } else {
        // Skip display of account chooser and consent dialog for an existing session.
        tokenClient.requestAccessToken({prompt: ''});
      }
    }

    function revokeToken() {
      let cred = gapi.client.getToken();
      if (cred !== null) {
        google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)});
        gapi.client.setToken('');
        document.getElementById("showEventsBtn").innerText = "Show Calendar";
      }
    }
  </script>
</body>
</html>

승인 코드 흐름 예시

Google ID 서비스 라이브러리 팝업 UX는 URL 리디렉션을 사용하여 승인 코드를 백엔드 토큰 엔드포인트로 직접 반환하거나 사용자 브라우저에서 실행되는 JavaScript 콜백 핸들러를 사용하여 플랫폼에 대한 응답을 프록시할 수 있습니다. 어떤 경우든 백엔드 플랫폼은 OAuth 2.0 흐름을 완료하여 유효한 갱신 및 액세스 토큰을 획득합니다.

이전 방식

서버 측 웹 앱

사용자 동의를 위해 Google로 리디렉션을 사용하는 백엔드 플랫폼에서 실행되는 서버 측 앱용 Google 로그인

<!DOCTYPE html>
<html>
  <head>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
    <script>
      function start() {
        gapi.load('auth2', function() {
          auth2 = gapi.auth2.init({
            client_id: 'YOUR_CLIENT_ID',
            api_key: 'YOUR_API_KEY',
            discovery_docs: ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
            // Scopes to request in addition to 'profile' and 'email'
            scope: 'https://www.googleapis.com/auth/cloud-translation',
          });
        });
      }
      function signInCallback(authResult) {
        if (authResult['code']) {
          console.log("sending AJAX request");
          // Send authorization code obtained from Google to backend platform
          $.ajax({
            type: 'POST',
            url: 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URL',
            // Always include an X-Requested-With header to protect against CSRF attacks.
            headers: {
              'X-Requested-With': 'XMLHttpRequest'
            },
            contentType: 'application/octet-stream; charset=utf-8',
            success: function(result) {
              console.log(result);
            },
            processData: false,
            data: authResult['code']
          });
        } else {
          console.log('error: failed to obtain authorization code')
        }
      }
    </script>
  </head>
  <body>
    <button id="signinButton">Sign In With Google</button>
    <script>
      $('#signinButton').click(function() {
        // Obtain an authorization code from Google
        auth2.grantOfflineAccess().then(signInCallback);
      });
    </script>
  </body>
</html>

리디렉션을 사용하는 HTTP/REST

웹 서버 애플리케이션용 OAuth 2.0 사용을 사용하여 사용자의 브라우저에서 백엔드 플랫폼으로 승인 코드를 전송합니다. 사용자의 브라우저를 Google로 리디렉션하여 사용자 동의를 처리합니다.

/\*
 \* Create form to request access token from Google's OAuth 2.0 server.
 \*/
function oauthSignIn() {
  // Google's OAuth 2.0 endpoint for requesting an access token
  var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
  // Create &lt;form> element to submit parameters to OAuth 2.0 endpoint.
  var form = document.createElement('form');
  form.setAttribute('method', 'GET'); // Send as a GET request.
  form.setAttribute('action', oauth2Endpoint);
  // Parameters to pass to OAuth 2.0 endpoint.
  var params = {'client\_id': 'YOUR_CLIENT_ID',
                'redirect\_uri': 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URL',
                'response\_type': 'token',
                'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly',
                'include\_granted\_scopes': 'true',
                'state': 'pass-through value'};
  // Add form parameters as hidden input values.
  for (var p in params) {
    var input = document.createElement('input');
    input.setAttribute('type', 'hidden');
    input.setAttribute('name', p);
    input.setAttribute('value', params[p]);
    form.appendChild(input);
  }

  // Add form to page and submit it to open the OAuth 2.0 endpoint.
  document.body.appendChild(form);
  form.submit();
}

새로운 방식

GIS 팝업 UX

이 예시에서는 승인 코드 모델을 사용하는 Google ID 서비스 JavaScript 라이브러리, 사용자 동의를 위한 팝업 대화상자, Google에서 승인 코드를 수신하는 콜백 핸들러만 보여줍니다. 클라이언트를 구성하고, 동의를 얻고, 승인 코드를 백엔드 플랫폼으로 전송하는 데 필요한 최소 단계 수를 보여주기 위해 제공됩니다.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      function initClient() {
        client = google.accounts.oauth2.initCodeClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly',
          ux_mode: 'popup',
          callback: (response) => {
            var code_receiver_uri = 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URI',
            // Send auth code to your backend platform
            const xhr = new XMLHttpRequest();
            xhr.open('POST', code_receiver_uri, true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.onload = function() {
              console.log('Signed in as: ' + xhr.responseText);
            };
            xhr.send('code=' + response.code);
            // After receipt, the code is exchanged for an access token and
            // refresh token, and the platform then updates this web app
            // running in user's browser with the requested calendar info.
          },
        });
      }
      function getAuthCode() {
        // Request authorization code and obtain user consent
        client.requestCode();
      }
    </script>
    <button onclick="getAuthCode();">Load Your Calendar</button>
  </body>
</html>

GIS 리디렉션 UX

승인 코드 모델은 팝업 및 리디렉션 UX 모드를 지원하여 사용자별 승인 코드를 플랫폼에서 호스팅하는 엔드포인트로 전송합니다. 리디렉션 UX 모드는 다음과 같습니다.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      function initClient() {
        client = google.accounts.oauth2.initCodeClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly \
                  https://www.googleapis.com/auth/photoslibrary.readonly',
          ux_mode: 'redirect',
          redirect_uri: 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URI'
        });
      }
      // Request an access token
      function getAuthCode() {
        // Request authorization code and obtain user consent
        client.requestCode();
      }
    </script>
    <button onclick="getAuthCode();">Load Your Calendar</button>
  </body>
</html>

JavaScript 라이브러리

Google ID 서비스는 사용자 인증 및 승인에 사용되는 단일 JavaScript 라이브러리로, 여러 라이브러리 및 모듈에 있는 기능과 기능을 통합하고 대체합니다.

ID 서비스로 이전할 때 취해야 할 조치:

기존 JS 라이브러리 새 JS 라이브러리 참고
apis.google.com/js/api.js accounts.google.com/gsi/client 새 라이브러리를 추가하고 암시적 흐름을 따릅니다.
apis.google.com/js/client.js accounts.google.com/gsi/client 새 라이브러리와 승인 코드 흐름을 추가합니다.

라이브러리 빠른 참조

이전 Google 로그인 JavaScript 클라이언트 라이브러리와 Google ID 서비스 라이브러리의 객체 및 메서드 비교와 이전 중에 취해야 할 추가 정보 및 조치가 포함된 참고

변경 전 신규 참고
GoogleAuth 객체 및 관련 메서드:
GoogleAuth.attachClickHandler() 삭제
GoogleAuth.currentUser.get() 삭제
GoogleAuth.currentUser.listen() 삭제
GoogleAuth.disconnect() google.accounts.oauth2.revoke 오래된 것을 새 것으로 대체합니다. https://myaccount.google.com/permissions에서도 취소할 수 있습니다.
GoogleAuth.grantOfflineAccess() 삭제하려면 승인 코드 흐름을 따르세요.
GoogleAuth.isSignedIn.get() 삭제
GoogleAuth.isSignedIn.listen() 삭제
GoogleAuth.signIn() 삭제
GoogleAuth.signOut() 삭제
GoogleAuth.then() 삭제
GoogleUser 객체 및 관련 메서드:
GoogleUser.disconnect() google.accounts.id.revoke 오래된 것을 새 것으로 대체합니다. https://myaccount.google.com/permissions에서도 취소할 수 있습니다.
GoogleUser.getAuthResponse() requestCode() or requestAccessToken() 오래된 항목을 새 항목으로 바꾸기
GoogleUser.getBasicProfile() 삭제를 탭합니다. 대신 ID 토큰을 사용하세요(Google 로그인에서 이전 참고).
GoogleUser.getGrantedScopes() hasGrantedAnyScope() 오래된 항목을 새 항목으로 바꾸기
GoogleUser.getHostedDomain() 삭제
GoogleUser.getId() 삭제
GoogleUser.grantOfflineAccess() 삭제하려면 승인 코드 흐름을 따르세요.
GoogleUser.grant() 삭제
GoogleUser.hasGrantedScopes() hasGrantedAnyScope() 오래된 항목을 새 항목으로 바꾸기
GoogleUser.isSignedIn() 삭제
GoogleUser.reloadAuthResponse() requestAccessToken() 만료되거나 취소된 액세스 토큰을 대체하기 위해 이전 토큰을 삭제하고 새 토큰을 호출합니다.
gapi.auth2 객체 및 관련 메서드:
gapi.auth2.AuthorizeConfig 객체 TokenClientConfig 또는 CodeClientConfig 오래된 항목을 새 항목으로 바꾸기
gapi.auth2.AuthorizeResponse 객체 삭제
gapi.auth2.AuthResponse 객체 삭제
gapi.auth2.authorize() requestCode() or requestAccessToken() 오래된 항목을 새 항목으로 바꾸기
gapi.auth2.ClientConfig() TokenClientConfig 또는 CodeClientConfig 오래된 항목을 새 항목으로 바꾸기
gapi.auth2.getAuthInstance() 삭제
gapi.auth2.init() initTokenClient() or initCodeClient() 오래된 항목을 새 항목으로 바꾸기
gapi.auth2.OfflineAccessOptions 객체 삭제
gapi.auth2.SignInOptions 객체 삭제
gapi.signin2 객체 및 관련 메서드:
gapi.signin2.render() 삭제를 탭합니다. g_id_signin 요소의 HTML DOM 로드 또는 google.accounts.id.renderButton에 대한 JS 호출은 Google 계정에 대한 사용자 로그인을 트리거합니다.

사용자 인증 정보 예

기존 사용자 인증 정보

Google 로그인 플랫폼 라이브러리, JavaScript용 Google API 클라이언트 라이브러리 또는 Google OAuth 2.0 엔드포인트에 대한 직접 호출은 단일 응답에서 OAuth 2.0 액세스 토큰과 OpenID Connect ID 토큰을 모두 반환합니다.

access_tokenid_token를 모두 포함하는 응답의 예:

  {
    "token_type": "Bearer",
    "access_token": "ya29.A0ARrdaM-SmArZaCIh68qXsZSzyeU-8mxhQERHrP2EXtxpUuZ-3oW8IW7a6D2J6lRnZrRj8S6-ZcIl5XVEqnqxq5fuMeDDH_6MZgQ5dgP7moY-yTiKR5kdPm-LkuPM-mOtUsylWPd1wpRmvw_AGOZ1UUCa6UD5Hg",
    "scope": "https://www.googleapis.com/auth/calendar.readonly",
    "login_hint": "AJDLj6I2d1RH77cgpe__DdEree1zxHjZJr4Q7yOisoumTZUmo5W2ZmVFHyAomUYzLkrluG-hqt4RnNxrPhArd5y6p8kzO0t8xIfMAe6yhztt6v2E-_Bb4Ec3GLFKikHSXNh5bI-gPrsI",
    "expires_in": 3599,
    "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjkzNDFhYmM0MDkyYjZmYzAzOGU0MDNjOTEwMjJkZDNlNDQ1MzliNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXpwIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTE3NzI2NDMxNjUxOTQzNjk4NjAwIiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJkYWJyaWFuQGdvb2dsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IkJBSW55TjN2MS1ZejNLQnJUMVo0ckEiLCJuYW1lIjoiQnJpYW4gRGF1Z2hlcnR5IiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hLS9BT2gxNEdnenAyTXNGRGZvbVdMX3VDemRYUWNzeVM3ZGtxTE5ybk90S0QzVXNRPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IkJyaWFuIiwiZmFtaWx5X25hbWUiOiJEYXVnaGVydHkiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTYzODk5MTYzOCwiZXhwIjoxNjM4OTk1MjM4LCJqdGkiOiI5YmRkZjE1YWFiNzE2ZDhjYmJmNDYwMmM1YWM3YzViN2VhMDQ5OTA5In0.K3EA-3Adw5HA7O8nJVCsX1HmGWxWzYk3P7ViVBb4H4BoT2-HIgxKlx1mi6jSxIUJGEekjw9MC-nL1B9Asgv1vXTMgoGaNna0UoEHYitySI23E5jaMkExkTSLtxI-ih2tJrA2ggfA9Ekj-JFiMc6MuJnwcfBTlsYWRcZOYVw3QpdTZ_VYfhUu-yERAElZCjaAyEXLtVQegRe-ymScra3r9S92TA33ylMb3WDTlfmDpWL0CDdDzby2asXYpl6GQ7SdSj64s49Yw6mdGELZn5WoJqG7Zr2KwIGXJuSxEo-wGbzxNK-mKAiABcFpYP4KHPEUgYyz3n9Vqn2Tfrgp-g65BQ",
    "session_state": {
      "extraQueryParams": {
        "authuser": "0"
      }
    },
    "first_issued_at": 1638991637982,
    "expires_at": 1638995236982,
    "idpId": "google"
  }

Google ID 서비스 사용자 인증 정보

Google ID 서비스 라이브러리에서 다음을 반환합니다.

  • 승인에 사용되는 경우 액세스 토큰

    {
      "access_token": "ya29.A0ARrdaM_LWSO-uckLj7IJVNSfnUityT0Xj-UCCrGxFQdxmLiWuAosnAKMVQ2Z0LLqeZdeJii3TgULp6hR_PJxnInBOl8UoUwWoqsrGQ7-swxgy97E8_hnzfhrOWyQBmH6zs0_sUCzwzhEr_FAVqf92sZZHphr0g",
      "token_type": "Bearer",
      "expires_in": 3599,
      "scope": "https://www.googleapis.com/auth/calendar.readonly"
    }
    
  • 또는 인증에 사용되는 경우 ID 토큰:

    {
      "clientId": "538344653255-758c5h5isc45vgk27d8h8deabovpg6to.apps.googleusercontent.com",
      "credential": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImMxODkyZWI0OWQ3ZWY5YWRmOGIyZTE0YzA1Y2EwZDAzMjcxNGEyMzciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2MzkxNTcyNjQsImF1ZCI6IjUzODM0NDY1MzI1NS03NThjNWg1aXNjNDV2Z2syN2Q4aDhkZWFib3ZwZzZ0by5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjExNzcyNjQzMTY1MTk0MzY5ODYwMCIsIm5vbmNlIjoiZm9vYmFyIiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJkYWJyaWFuQGdvb2dsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwibmFtZSI6IkJyaWFuIERhdWdoZXJ0eSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQU9oMTRHZ3pwMk1zRkRmb21XTF91Q3pkWFFjc3lTN2RrcUxOcm5PdEtEM1VzUT1zOTYtYyIsImdpdmVuX25hbWUiOiJCcmlhbiIsImZhbWlseV9uYW1lIjoiRGF1Z2hlcnR5IiwiaWF0IjoxNjM5MTU3NTY0LCJleHAiOjE2MzkxNjExNjQsImp0aSI6IjRiOTVkYjAyZjU4NDczMmUxZGJkOTY2NWJiMWYzY2VhYzgyMmI0NjUifQ.Cr-AgMsLFeLurnqyGpw0hSomjOCU4S3cU669Hyi4VsbqnAV11zc_z73o6ahe9Nqc26kPVCNRGSqYrDZPfRyTnV6g1PIgc4Zvl-JBuy6O9HhClAK1HhMwh1FpgeYwXqrng1tifmuotuLQnZAiQJM73Gl-J_6s86Buo_1AIx5YAKCucYDUYYdXBIHLxrbALsA5W6pZCqqkMbqpTWteix-G5Q5T8LNsfqIu_uMBUGceqZWFJALhS9ieaDqoxhIqpx_89QAr1YlGu_UO6R6FYl0wDT-nzjyeF5tonSs3FHN0iNIiR3AMOHZu7KUwZaUdHg4eYkU-sQ01QNY_11keHROCRQ",
      "select_by": "user"
    }
    

잘못된 토큰 응답

만료되었거나, 취소되었거나, 잘못된 액세스 토큰을 사용하여 API 요청을 시도할 때 Google에서 반환하는 응답의 예:

HTTP 응답 헤더

  www-authenticate: Bearer realm="https://accounts.google.com/", error="invalid_token"

응답 본문

  {
    "error": {
      "code": 401,
      "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
      "errors": [
        {
          "message": "Invalid Credentials",
          "domain": "global",
          "reason": "authError",
          "location": "Authorization",
          "locationType": "header"
        }
      ],
      "status": "UNAUTHENTICATED"
    }
  }