App Engine에서 Firebase로 사용자 인증


이 가이드는 Firebase 인증, App Engine 표준 환경, Datastore를 사용하여 사용자 인증 정보를 검색, 확인, 저장하는 방법을 보여줍니다.

이 문서는 사용자의 개인 메모장에 사용자 메모를 저장하는 Firenotes라는 간단한 메모 작성 애플리케이션을 안내합니다. 메모장은 사용자별로 저장되며 각 사용자의 고유한 Firebase 인증 ID로 식별됩니다. 이 애플리케이션에는 다음과 같은 구성 요소가 있습니다.

  • 프런트엔드는 로그인 사용자 인터페이스를 구성하고 Firebase 인증 ID를 검색합니다. 또한 인증 상태 변경을 처리하고 사용자가 메모를 볼 수 있게 해줍니다.

  • FirebaseUI는 인증 및 UI 작업을 간소화하는 오픈소스 삽입형 솔루션입니다. SDK는 사용자 로그인을 처리하고 여러 공급업체를 하나의 계정에 연결하며 비밀번호를 복구하는 등의 작업을 처리합니다. 또한 원활하고 안전한 로그인 환경을 위한 인증 권장사항을 구현합니다.

    FirebaseUI
  • 백엔드는 사용자의 인증 상태를 확인하고 사용자의 프로필 정보 및 메모를 반환합니다.

이 애플리케이션은 NDB 클라이언트 라이브러리를 사용하여 Datastore에 사용자 인증 정보를 저장하지만, 필요에 따라 다른 데이터베이스에 사용자 인증 정보를 저장할 수 있습니다.

아래 다이어그램은 프런트엔드와 백엔드가 서로 통신하는 방법과 사용자 인증 정보가 Firebase에서 데이터베이스로 전달되는 방법을 보여줍니다.
사용자 인증 정보가 있는 요청의 경로

Firenotes는 Flask 웹 애플리케이션 프레임워크를 기반으로 합니다. 샘플 앱에서는 단순성과 편의성으로 인해 Flask를 사용하지만, 여기에서 설명하는 개념과 기술은 어떠한 프레임워크에도 적용될 수 있습니다.

목표

이 가이드를 완료하면 다음을 수행할 수 있습니다.

  • Firebase 인증 사용자 인터페이스를 구성합니다.
  • Firebase ID 토큰을 획득하고 서버 측 인증을 사용하여 확인합니다.
  • Datastore에 사용자 인증 정보 및 관련 데이터를 저장합니다.
  • NDB 클라이언트 라이브러리를 사용하여 데이터베이스를 쿼리합니다.
  • App Engine에 앱을 배포합니다.

비용

이 가이드에서는 다음과 같은 비용이 청구될 수 있는 Google Cloud 구성요소를 사용합니다.

  • Datastore

가격 계산기를 사용하면 예상 사용량을 기준으로 예상 비용을 산출할 수 있습니다. Google Cloud를 처음 사용하는 사용자는 무료 체험판을 사용할 수 있습니다.

시작하기 전에

  1. Git, Python 2.7, virtualenv를 설치합니다. 최신 버전의 Python을 설치하는 등의 Python 개발 환경 설정에 관한 자세한 내용은 Google Cloud용 Python 개발 환경 설정을 참조하세요.
  2. Google Cloud 계정에 로그인합니다. Google Cloud를 처음 사용하는 경우 계정을 만들고 Google 제품의 실제 성능을 평가해 보세요. 신규 고객에게는 워크로드를 실행, 테스트, 배포하는 데 사용할 수 있는 $300의 무료 크레딧이 제공됩니다.
  3. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

  4. Google Cloud CLI를 설치합니다.
  5. gcloud CLI를 초기화하려면 다음 명령어를 실행합니다.

    gcloud init
  6. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

  7. Google Cloud CLI를 설치합니다.
  8. gcloud CLI를 초기화하려면 다음 명령어를 실행합니다.

    gcloud init

SDK를 이미 다른 프로젝트에 설치하고 초기화한 경우 gcloud 프로젝트를 Firenotes용으로 사용 중인 App Engine 프로젝트 ID에 추가합니다. gcloud 도구로 프로젝트를 업데이트하는 구체적인 명령어는 Google Cloud SDK 구성 관리를 참조하세요.

샘플 앱 복제

로컬 머신에 샘플을 다운로드하는 방법은 다음과 같습니다.

  1. 샘플 애플리케이션 저장소를 로컬 머신에 복제합니다.

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    또는 ZIP 파일로 샘플을 다운로드하고 압축을 풉니다.

  2. 샘플 코드가 있는 디렉터리로 이동합니다.

    cd python-docs-samples/appengine/standard/firebase/firenotes
    
    FirebaseUI를 구성하고 ID 공급업체를 사용 설정하는 방법은 다음과 같습니다.

  3. 다음 단계에 따라 앱에 Firebase를 추가합니다.

    1. Firebase Console에서 Firebase 프로젝트를 만듭니다.
      • Firebase 프로젝트가 없으면 프로젝트 추가를 클릭하고 기존의 Google Cloud 프로젝트 이름 또는 새 프로젝트 이름을 입력합니다.
      • 사용할 기존 Firebase 프로젝트가 있으면 Console에서 그 프로젝트를 선택합니다.
    2. 프로젝트 개요 페이지에서 웹 앱에 Firebase 추가를 클릭합니다. 프로젝트에 이미 앱이 있는 경우 프로젝트 개요 페이지에서 앱 추가를 선택합니다.
    3. 프로젝트의 맞춤설정된 코드 스니펫에서 Initialize Firebase 섹션을 사용하여 frontend/main.js 파일의 다음 섹션을 작성합니다.

      // Obtain the following from the "Add Firebase to your web app" dialogue
      // Initialize Firebase
      var config = {
        apiKey: "<API_KEY>",
        authDomain: "<PROJECT_ID>.firebaseapp.com",
        databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
        projectId: "<PROJECT_ID>",
        storageBucket: "<BUCKET>.appspot.com",
        messagingSenderId: "<MESSAGING_SENDER_ID>"
      };
  4. frontend/main.js 파일에서 사용자에게 제공할 제공 업체를 선택하여 FirebaseUI 로그인 위젯을 구성합니다.

    // Firebase log-in widget
    function configureFirebaseLoginWidget() {
      var uiConfig = {
        'signInSuccessUrl': '/',
        'signInOptions': [
          // Leave the lines as is for the providers you want to offer your users.
          firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          firebase.auth.FacebookAuthProvider.PROVIDER_ID,
          firebase.auth.TwitterAuthProvider.PROVIDER_ID,
          firebase.auth.GithubAuthProvider.PROVIDER_ID,
          firebase.auth.EmailAuthProvider.PROVIDER_ID
        ],
        // Terms of service url
        'tosUrl': '<your-tos-url>',
      };
    
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      ui.start('#firebaseui-auth-container', uiConfig);
    }
  5. 인증 > 로그인 방법을 클릭하여 Firebase Console에서 유지할 제공업체를 사용 설정합니다. 그리고 로그인 제공업체에서 원하는 제공업체로 마우스를 가져간 다음 연필 아이콘을 클릭합니다.

    로그인 제공업체

    1. 사용 설정 버튼을 전환하고 타사 ID 공급업체인 경우 제공업체의 개발자 사이트에서 확인한 제공업체 ID와 비밀번호를 입력합니다. 구체적인 안내는 Firebase 문서 중에서 Facebook, Twitter, GitHub 가이드의 '시작하기 전에' 섹션을 참조하세요. 공급업체를 사용 설정한 후 저장을 클릭합니다.

      사용 설정 버튼 전환

    2. Firebase Console에서 승인된 도메인 아래의 도메인 추가를 클릭하고 App Engine에 있는 앱의 도메인을 다음 형식으로 입력합니다.

      [PROJECT_ID].appspot.com
      

      도메인 이름 앞에 http://를 포함하지 마세요.

종속 항목 설치

  1. backend 디렉터리로 이동하여 애플리케이션 설정을 완료합니다.

    cd backend/
    
  2. 프로젝트의 lib 디렉터리에 종속 항목을 설치합니다.

    pip install -t lib -r requirements.txt
    
  3. appengine_config.py에서 vendor.add() 메서드는 lib 디렉터리에 라이브러리를 등록합니다.

애플리케이션을 로컬로 실행

애플리케이션을 로컬로 실행하려면 App Engine 로컬 개발 서버를 사용합니다.

  1. 다음 URL을 main.jsbackendHostURL로 추가합니다.

    http://localhost:8081

  2. 애플리케이션의 루트 디렉터리로 이동합니다. 그런 다음 개발 서버를 시작합니다.

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. 웹브라우저에서 http://localhost:8080/을 방문합니다.

서버에서 사용자 인증

이제 프로젝트를 설정했고 개발을 위해 애플리케이션을 초기화했으므로, 코드를 살펴보면서 서버에서 Firebase ID 토큰을 검색하고 확인하는 방법을 알아보겠습니다.

Firebase에서 ID 토큰 가져오기

서버 측 인증의 첫 번째 단계는 확인할 액세스 토큰을 검색하는 것입니다. 인증 요청은 Firebase의 onAuthStateChanged() 리스너가 처리합니다.

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    $('#logged-out').hide();
    var name = user.displayName;

    /* If the provider gives a display name, use the name for the
    personal welcome message. Otherwise, use the user's email. */
    var welcomeName = name ? name : user.email;

    user.getIdToken().then(function(idToken) {
      userIdToken = idToken;

      /* Now that the user is authenicated, fetch the notes. */
      fetchNotes();

      $('#user').text(welcomeName);
      $('#logged-in').show();

    });

  } else {
    $('#logged-in').hide();
    $('#logged-out').show();

  }
});

사용자가 로그인하면 콜백의 Firebase getToken() 메서드가 JSON 웹 토큰(JWT) 형식으로 Firebase ID 토큰을 반환합니다.

서버에서 토큰 확인

사용자가 로그인한 후 프런트엔드 서비스는 AJAX GET 요청을 통해 사용자의 메모장에 있는 기존 메모장을 가져옵니다. 이 때 사용자의 데이터에 액세스하려면 승인이 필요하므로 Bearer 스키마를 사용하여 요청의 Authorization 헤더를 통해 JWT를 전송합니다.

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  })

클라이언트가 서버 데이터에 액세스하려면 먼저 토큰이 Firebase에 의해 서명되었는지 여부를 서버가 확인해야 합니다. Python용 Google 인증 라이브러리를 사용하여 이 토큰을 확인할 수 있습니다. 인증 라이브러리의 verify_firebase_token 함수를 사용하여 Bearer 토큰을 확인하고 클레임을 추출합니다.

id_token = request.headers["Authorization"].split(" ").pop()
claims = google.oauth2.id_token.verify_firebase_token(
    id_token, HTTP_REQUEST, audience=os.environ.get("GOOGLE_CLOUD_PROJECT")
)
if not claims:
    return "Unauthorized", 401

각 ID 공급업체는 서로 다른 클레임 집합을 전송하지만 각각의 클레임에는 순 사용자 ID를 가진 sub 클레임 및 name 또는 email와 같이 앱에서 사용자 환경을 맞춤설정할 수 있는 일부 프로필 정보를 제공하는 클레임을 적어도 하나씩 가지고 있습니다.

Datastore에서 사용자 데이터 관리

사용자를 인증한 후에는 로그인 세션이 끝나도 사용자의 데이터가 유지되도록 데이터를 저장해야 합니다. 다음 섹션에서는 메모를 Datastore 항목으로 저장하고 사용자 ID에 따라 항목을 분리하는 방법에 대해 설명합니다.

사용자 데이터를 저장하는 항목 만들기

정수 또는 문자열과 같은 특정 속성을 가지고 있는 NDB 모델 클래스를 선언하여 Datastore에서 항목을 만들 수 있습니다. Datastore는 종류별로 항목의 색인을 생성합니다. Firenotes의 경우 각 항목의 종류는 Note입니다. 쿼리용으로 각 Note키 이름과 함께 저장되며, 키 이름은 이전 섹션의 sub 클레임에서 가져온 사용자 ID입니다.

다음 코드에서는 항목의 속성을 설정하기 위해 항목을 만들 때 모델 클래스의 생성자 메서드를 사용하는 방법과 생성 후 개별 속성을 할당하는 방법을 모두 보여줍니다.

data = request.get_json()

# Populates note properties according to the model,
# with the user ID as the key name.
note = Note(parent=ndb.Key(Note, claims["sub"]), message=data["message"])

# Some providers do not provide one of these so either can be used.
note.friendly_id = claims.get("name", claims.get("email", "Unknown"))

새로 생성된 Note를 Datastore에 기록하려면 note 객체에 대해 put() 메서드를 호출합니다.

사용자 데이터 검색

특정 사용자 ID와 연관된 사용자 데이터를 검색하려면 NDB query() 메서드를 사용하여 동일한 항목 그룹에서 메모용 데이터베이스를 검색합니다. 같은 그룹, 즉 동일한 상위 경로에 속한 항목은 공통 키 이름(여기서는 사용자 ID)을 공유합니다.

def query_database(user_id):
    """Fetches all notes associated with user_id.

    Notes are ordered them by date created, with most recent note added
    first.
    """
    ancestor_key = ndb.Key(Note, user_id)
    query = Note.query(ancestor=ancestor_key).order(-Note.created)
    notes = query.fetch()

    note_messages = []

    for note in notes:
        note_messages.append(
            {
                "friendly_id": note.friendly_id,
                "message": note.message,
                "created": note.created,
            }
        )

    return note_messages

그런 다음 쿼리 데이터를 가져와서 클라이언트에 메모를 표시합니다.

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  }).then(function(data){
    $('#notes-container').empty();
    // Iterate over user data to display user's notes from database.
    data.forEach(function(note){
      $('#notes-container').append($('<p>').text(note.message));
    });
  });
}

앱 배포

이제 Firebase 인증이 App Engine 애플리케이션에 통합되었습니다. 실제 프로덕션 환경에서 애플리케이션의 실행을 확인하려면 다음 안내를 따르세요.

  1. main.js의 백엔드 호스트 URL을 https://backend-dot-[PROJECT_ID].appspot.com으로 변경합니다. 여기서 [PROJECT_ID]를 프로젝트 ID로 바꿉니다.
  2. Google Cloud SDK 명령줄 인터페이스를 사용하여 애플리케이션을 배포합니다.

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. https://[PROJECT_ID].appspot.com에서 애플리케이션의 실제 작동을 확인합니다.

삭제

이 튜토리얼에서 사용한 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 App Engine 프로젝트를 삭제하세요.

프로젝트 삭제

비용이 청구되지 않도록 하는 가장 쉬운 방법은 튜토리얼에서 만든 프로젝트를 삭제하는 것입니다.

프로젝트를 삭제하는 방법은 다음과 같습니다.

  1. Google Cloud 콘솔에서 리소스 관리 페이지로 이동합니다.

    리소스 관리로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 후 종료를 클릭하여 프로젝트를 삭제합니다.

다음 단계

  • Google Cloud에 대한 참조 아키텍처, 다이어그램, 권장사항 살펴보기 Cloud 아키텍처 센터 살펴보기