Создание минимального приложения Python App Engine, реализующего аутентификацию Firebase

В этой предыдущей статье я сказал, что в новом году у меня есть решение прекратить напрямую заниматься аутентификацией пользователей, и написал об одновременном использовании Firebase и App Engine.



В этой статье я разрабатываю минимальное приложение на Python на App Engine (с использованием Flask на внутренней стороне), которое реализует Firebase Authentication.

Вот архитектура, которую я буду использовать.

Это упрощенная версия архитектуры Cat Dog As A Service из предыдущей статьи, в которой мы просто выполняем аутентификацию.

1 - Клиент будет аутентифицироваться через Firebase Authentication.

2. После этого у него будет токен аутентификации, который будет использоваться при разговоре с App Engine.

В этой статье приложение, которое я создаю, просто позволяет входить и выходить через Firebase. Затем в следующей статье / приложении мы поговорим о серверных API, проверим аутентификацию Firebase и будем действовать соответствующим образом.

1. Начните с приложения-скелета

Некоторое время назад я сделал скелет приложения для приложений на основе Flask. Вот репо:



Вот статья об этом:



Так что я скопировал это в свой действительноauthul1 проект.

Давайте запустим его, чтобы запомнить, что он делает:

Как мне его запустить?

  • Я запустил scripts/setup.sh, чтобы получить библиотеки Python с помощью pip
  • Я запускал его локально, используя scripts/runlocal.sh

Хорошо, хорошо. Как это работает?

У меня есть src/app.yaml, в котором написано

runtime: python27
api_version: 1
threadsafe: true
automatic_scaling:
max_idle_instances: 2
handlers:
- url: /static
  static_dir: static
- url: /.*
  script: main.app

Последний бит говорит мне искать src/main.py; это выглядит так:

import logging
from flask import Flask
app = Flask(__name__)
from handlers.helloworld import get_helloworld
get_helloworld(app)
@app.errorhandler(500)
def server_error(e):
  # Log the error and stacktrace.
  logging.exception('An error occurred during a request.')
  return 'An internal error occurred.', 500

Хорошо, у нас есть обработчик helloworld, зарегистрированный во Flask в src/handlers/helloworld. А это выглядит так:

from flask import render_template
def get_helloworld(app):
  @app.route('/', methods=["GET", "POST"])
  def helloworld():
    return render_template("helloworld.html")

Мы обрабатываем шаблон helloworld.html. Я вижу одну в src/templates, но как она ее обнаруживает? О, здесь в документе Flask:

Итак, helloworld.html - это шаблон jinja2, и он находится в относительной папке templates. Хм, это больше волшебства, чем я мог бы реализовать, но ладно, народ Flask, круто.

Наконец, вот src/templates/helloworld.html:

<html>
  <head>
    <title>Hello World!</title>
    <link rel="stylesheet" type="text/css" href="static/main.css">
  </head>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>

Революционный материал.

2. Давайте изменим его на "Настоящая аутентичность"

Итак, я хочу продемонстрировать аутентификацию Firebase. ОТХ, я очень ленивый человек. Что делать?

Как можно меньше звучит хорошо.

Хорошо, давайте изменим обработчик Hello World на "Truly Authul". Кроме того, давайте укажем имя или адрес электронной почты пользователя или все, что мы можем получить.

Обновите main.py:

from handlers.trulyauthul import get_trulyauthul
get_trulyauthul(app)

Теперь нам понадобится src/handlers/trulyauthul.py:

from flask import render_template#, request, redirect
def get_trulyauthul(app):
  @app.route('/', methods=["GET", "POST"])
  def trulyauthul():
    return render_template("trulyauthul.html")

и src/templates/trulyauthul.html:

<html>
  <head>
    <title>Truly Authul!</title>
    <link rel="stylesheet" type="text/css" href="static/main.css">
  </head>
  <body>
    <h1>Hello there whatsisname, welcome to Truly Authul!</h1>
  </body>
</html>

Хорошо, поехали!

Хорошо хорошо.

3. Давайте добавим аутентификацию Firebase.

Итак, что я хочу сделать сейчас, так это потребовать от пользователя аутентификации, прежде чем он увидит внутреннее сообщение Truly Authul.

Пока что веб-страница отображается в бэк-энде. Но Firebase Authentication действительно хочет работать в SPA (одностраничном приложении).

Итак, каков самый ленивый способ добиться этого?

Давайте просто придерживаемся шаблона trueauthul.html, но мы собираемся добавить его с помощью JavaScript и настроить firebase. Он проверит, прошли ли вы аутентификацию, и либо заставит вас пройти аутентификацию, либо покажет вам приветственное сообщение, если вы аутентифицированы.

Взглянув на документацию Firebase Authentication, подход Firebase UI выглядит довольно ленивым; проверить это:

‹Переходы по Интернету›

Я оказался в этом репозитории Github:



О, Readme законный. Давайте рассмотрим шаг за шагом.

CDN означает отсутствие библиотек или чего-то подобного. Легко, давайте сделаем это, просто включите эти строки… о, но «под фрагментом инициализации из Firebase Console». Хм.

Что ж, на самом деле мы немного забежали вперед.

4. Настройка Cat Dog как службы (проект App Engine + Firebase)

Чтобы использовать Firebase, нам нужно прекратить попытки разрабатывать локально (бум!) И начать настройку в Интернете. Мне нужно создать проект App Engine, связать его с проектом Firebase, а затем в консоли Firebase я найду скрытый фрагмент.

Итак, я перехожу в Google Cloud Platform и создаю новый проект:

Вау, осталось 476 проектов? Последнее, что я помню с App Engine, я был ограничен 10 или 15 или около того. Большой!

Хорошо, теперь перейдем в Firebase и создадим там проект.

Создайте этот проект! Он также попросил у меня тарифный план, Блейз прав (просто платите по мере использования). Теперь вы увидите главную страницу консоли. Выберите «Добавить Firebase в свое веб-приложение»…

… И вы это понимаете.

Это фрагмент инициализации из консоли Firebase!

Обратите внимание на эти противоречивые сообщения:

Хорошо, я поставила его наверху и посмотрим, как у нас получится. Вот src/templates/trulyauthul.htmlnow:

<html>
  <head>
    <title>Truly Authul!</title>
    <link rel="stylesheet" type="text/css" href="static/main.css">
    <script src="https://www.gstatic.com/firebasejs/4.9.0/firebase.js"></script>
    <script>
      // Initialize Firebase
      var config = {
        apiKey: "AIzaSyDTd_xbonBWasEIdlczX-J6v3OlQ8TWtBA",
        authDomain: "trulyauthul.firebaseapp.com",
        databaseURL: "https://trulyauthul.firebaseio.com",
        projectId: "trulyauthul",
        storageBucket: "trulyauthul.appspot.com",
        messagingSenderId: "526098954239"
      };
      firebase.initializeApp(config);
    </script>
    <script src="https://cdn.firebase.com/libs/firebaseui/2.5.1/firebaseui.js">
    </script>
    <link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.5.1/firebaseui.css" />
  </head>
  <body>
    <h1>Hello there whatsisname, welcome to Truly Authul!</h1>
  </body>
</html>

Хорошо, предполагалось, что именно так мы настроим Firebase и Firebase UI. Теперь давайте развернем его, запустим в сети и посмотрим, что произойдет.

Мне нужно отредактировать scripts/deploy.sh, чтобы исправить развертывание движка приложения для этого проекта. Обратите внимание: нам не нужно ничего развертывать в Firebase. Вот scripts/deploy.sh:

cd ../src
gcloud app deploy app.yaml --project <<yourprojectname>> --version defaultversion --verbosity=info

Беги, жужжание, жужжание, жужжание и успех! Пойдем взглянем:

Здесь ничего интересного не происходит. OTOH, мы не должны были ожидать многого; Firebase настроена, но на самом деле мы ее не вызываем. Для этого нам нужно вернуться к файлу Readme репозитория Firebase UI git.

5. Подключение пользовательского интерфейса Firebase, чтобы он что-то делал

Вернемся к Readme. Следующий бит:

Гм, я уже начал благодарить.

Да, это похоже на PITA. Давайте просто выберем один, и мы сможем расширить его позже. Я всегда использую Google для аутентификации, так что давайте смешаем это и сделаем Facebook. Нажимаем, получаем вот это:

Я выполнил шаг 1. Шаг 2 гласит, что мы отправляемся в Facebook для разработчиков и регистрируем Truly Authul как приложение Facebook.

Facebook для разработчиков? Я не был там с тех пор, как появился Zynga. Нажмите…

Вы знаете, я полагаю, это это глобальный масштаб, не так ли? Это интарвебы, и вы, может быть, находитесь в какой-то другой стране, чем я.

Хорошо! Загорелся! Давайте сделаем это, Facebook, давайте выйдем на глобальный уровень! Теперь, чтобы зарегистрировать мое приложение и получить идентификатор приложения и секрет приложения…

Я даже не могу. Может быть, Google сможет помочь. На самом деле она может, она дает мне эту ссылку:



который включает следующий список:

Похоже, это огромная ужасная работа, мы на правильном пути. Мой план - сделать здесь как можно меньше, а потом бросить дымовую шашку и исчезнуть.

Вход в Facebook сам по себе почти нарушает условия сделки; Ненавижу это делать. Но ладно.

Создание учетной записи разработчика просто вызывало ошибки и много неудач, но теперь я вижу, что я уже один из них.

(если сомневаетесь, зайдите на developers.facebook.com и монетизируйте что-нибудь)

Создайте новое приложение Facebook (это на developers.facebook.com):

достает мне это

И это похоже на победу:

Это идентификатор приложения, а секрет приложения находится в меню «Настройки». Понятно.

Редактировать: Я исчез слишком быстро. Оказалось, что только я могу использовать логин facebook; Мне пришлось опубликовать приложение facebook, чтобы сделать его доступным для других. Это очень просто сделать в консоли разработчика facebook.

Хорошо, вернемся к Инструкциям Firebase по настройке входа в Facebook, мы переходим к шагу 3:

Хорошо, мы заходим в консоль, добавляем App ID и App Secret:

Очевидно, мне нужно настроить перенаправление OAuth в конфигурации моего приложения Facebook. О, это:

Но что за шляпки, а? Как мне это сделать?

Оказывается, многие люди боролись с этими татерами. Вот ответ о переполнении стека:



Думаю, вот что:

Хорошо, это работает? Пока не знаю. Будем надеяться.

Итак, где мы сейчас?

6. Подключаем пользовательский интерфейс Firebase, чтобы он что-то делал (5 оказался о чепухе с Facebook)

Итак, есть еще больше инструкций, которые очень похожи на Facebook, о том, как работать с аутентификацией под битом «прежде, чем вы начнете», но давайте проигнорируем их (!) И вернемся к файлу readme пользовательского интерфейса Facebook.

Следуя их примеру, я получаю следующее:

<html>
  <head>
    <title>Truly Authul!</title>
    <link rel="stylesheet" type="text/css" href="static/main.css">
    <script src="https://www.gstatic.com/firebasejs/4.9.0/firebase.js"></script>
    <script>
      // Initialize Firebase
      var config = {
        apiKey: "AIzaSyDTd_xbonBWasEIdlczX-J6v3OlQ8TWtBA",
        authDomain: "trulyauthul.firebaseapp.com",
        databaseURL: "https://trulyauthul.firebaseio.com",
        projectId: "trulyauthul",
        storageBucket: "trulyauthul.appspot.com",
        messagingSenderId: "526098954239"
      };
      firebase.initializeApp(config);
    </script>
    <script src="https://cdn.firebase.com/libs/firebaseui/2.5.1/firebaseui.js">   
    </script>
    <link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.5.1/firebaseui.css" />
    <script type="text/javascript">
      // FirebaseUI config.
      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,
//          firebase.auth.PhoneAuthProvider.PROVIDER_ID
        ],
        // Terms of service url.
        tosUrl: 'https://goo.gl/Fbui2b'
      };
      // Initialize the FirebaseUI Widget using Firebase.
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      // The start method will wait until the DOM is loaded.
      ui.start('#firebaseui-auth-container', uiConfig);
    </script>
  </head>
  <body>
    <h1>Hello there whatsisname, welcome to Truly Authul!</h1>
    <div id="firebaseui-auth-container"></div>
  </body>
</html>

Хорошо ... что это будет делать? Похоже, пользовательский интерфейс Firebase создается, а затем отображается в firebaseui-auth-container div.

Выполняя повторное развертывание, я обновляю http://trulyauthul.appspot.com/ и получаю:

Ооо, похоже, пользовательский интерфейс firebase помещает туда кнопку входа! Давай щелкнем по нему!

“This domain (trulyauthul.appspot.com) is not authorized to run this operation. Add it to the OAuth redirect domains list in the Firebase console -> Auth section -> Sign in method tab.”

Ok. Что-то вроде этого?

Добавьте, затем обновите http://trulyauthul.appspot.com/ и попробуйте нажать еще раз.

А теперь немного действий! Я получаю это:

Я нажимаю «Продолжить как Эмлин», и меня перенаправляют обратно:

Oh.

Что ж, я полагаю, что на самом деле я ничего не делаю в своем javascript при успешном входе в систему, и даже не проверяю, вошел ли я в систему, или что-то в этом роде. Вы бы подумали, что это важно.

7. Давайте подключим эту штуку, чтобы мы знали, аутентифицирован ли пользователь.

Кажется, что шагов пока много. Думал будет проще.

В файле readme есть еще один пример, в котором говорится, что нужно поместить onStateChanged обработчик в службу Firebase auth, чтобы отслеживать, когда пользователь входит в систему или выходит из нее, чтобы вы могли делать такие вещи, как отображение сведений о вошедшем пользователе, предоставление кнопки выхода и т. Д.

Код, который они предоставляют, немного странный, и непонятно, как он сочетается с пользовательским интерфейсом Firebase, но я добавлю обработчик.

Что я уже заметил, так это то, что часть пользовательского интерфейса Firebase, кажется, совершенно не обращает внимания на то, вошли ли вы уже в систему. Итак, я изменю ситуацию, чтобы она отображалась только в том случае, если мы еще не вошли в систему. Достаточно просто.

Но как насчет того, если мы вошли в систему, как нам выйти? Рад, что ты спросил.

В образце кода об этом ничего не говорится, но пользовательский интерфейс Firebase позволяет нам войти в систему стандартным способом Firebase. Это означает, что нам доступен весь пакет Firebase SDK. Короче говоря, мы должны выйти из системы любым способом, стандартным для Firebase.

Нам нужно взглянуть на службу Firebase sdk под названием auth, которая посвящена аутентификации.



Оказывается, в службе аутентификации есть метод signOut (), то есть:

firebase.auth().signOut()

сделаю свою работу.

Учитывая все это, вот рабочая версия src/templates/trulyauthul.html:

<html>
  <head>
    <title>Truly Authul!</title>
    <link rel="stylesheet" type="text/css" href="static/main.css">
    <script src="https://www.gstatic.com/firebasejs/4.9.0/firebase.js"></script>
    <script>
      // Initialize Firebase
      var config = 
      {
        apiKey: "AIzaSyDTd_xbonBWasEIdlczX-J6v3OlQ8TWtBA",
        authDomain: "trulyauthul.firebaseapp.com",
        databaseURL: "https://trulyauthul.firebaseio.com",
        projectId: "trulyauthul",
        storageBucket: "trulyauthul.appspot.com",
        messagingSenderId: "526098954239"
      };
      firebase.initializeApp(config);
    </script>
    <script src="https://cdn.firebase.com/libs/firebaseui/2.5.1/firebaseui.js">    
    </script>
    <link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.5.1/firebaseui.css" />
    <script type="text/javascript">
      // FirebaseUI config.
      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,
          // firebase.auth.PhoneAuthProvider.PROVIDER_ID
        ],
        // Terms of service url.
        tosUrl: 'https://goo.gl/Fbui2b'
      };
      // Initialize the FirebaseUI Widget using Firebase.
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      initApp = function() {
        firebase.auth().onAuthStateChanged(function(user) {
          if (user) {
            // User is signed in.
            var displayName = user.displayName;
            var email = user.email;
            var emailVerified = user.emailVerified;
            var photoURL = user.photoURL;
            var uid = user.uid;
            var phoneNumber = user.phoneNumber;
            var providerData = user.providerData;
            user.getIdToken().then(function(accessToken) {
              document.getElementById('sign-in-status').textContent = 'Hi ' + user.displayName + ", welcome to Truly Authul!";
              document.getElementById('sign-out').style.display = 'block';
              document.getElementById('firebaseui-auth-container').style.display = 'none';
              document.getElementById('sign-out').textContent = 'Sign out';
              document.getElementById('sign-out').onclick = function(event){
             firebase.auth().signOut();
              };
 
            console.log("account details: " + JSON.stringify({
                displayName: displayName,
                email: email,
                emailVerified: emailVerified,
                phoneNumber: phoneNumber,
                photoURL: photoURL,
                uid: uid,
                accessToken: accessToken,
                providerData: providerData
              }, null, 2));
            });
          } else {
            // User is signed out.
            document.getElementById('sign-in-status').textContent = 'Hello there! Please sign in to access Truly Authul';
            document.getElementById('sign-out').style.display = 'none';
            document.getElementById('firebaseui-auth-container').style.display = 'block';
            ui.start('#firebaseui-auth-container', uiConfig);
          }
        }, function(error) {
          console.log(error);
        });
      };
      
      window.addEventListener('load', function() {
        initApp()
      });      
    </script>
  </head>
  <body>
    <div id="sign-in-status"></div>
    <button id="sign-out"></button>
    <div id="firebaseui-auth-container"></div>
  </body>
</html>

Посмотрим, что это делает. Развернуть, обновить и…

Хорошо, давай нажмем ...

Нажмите Sign out:

Вот это успех!

Заключительные мысли

Итак, мы сделали это, у нас заработала базовая аутентификация Firebase!

Вы можете попробовать это здесь:



И вы можете увидеть здесь код:



Это было непросто, но аутентификация с помощью Firebase дает много преимуществ, что, надеюсь, я продемонстрирую в следующих статьях.

В следующем посте я изменю Truly Authul, чтобы он взаимодействовал с серверным API в App Engine, этот API проверяет пользователя и возвращает что-то другое в зависимости от того, авторизованы мы или нет.

Спасибо за прочтение!