Пользователь
0,0
рейтинг
22 января 2015 в 05:22

Разработка → Сайт без бекэнда: аутентификация пользователя в BaaS parse.com через социальные сети tutorial

Я буду каждое утро развертывать мир, как резиновую ленту на мяче для гольфа, а вечером завертывать обратно. Если очень попросишь — покажу, как это делается.
Р. Брэдбери

UPDATE: Parse не будет работать к 2017 году.

Введение


В статье описан Backend-as-a-Service подход к хранению и обработки данных. Рассказаны преимущества и недостатки представителя такого подхода — сервиса parse.com. Коротко представлен сервис аутентификации пользователей через соц. сети uLogin. Основное назначение — показать, как эти два сервиса могут взаимодействовать, чтобы проект не требовал регистрации пользователей по логину и паролю, но в то же время сохранилась возможность авторизации пользователей к действиям над объектами.

О BaaS и parse.com


Parse.com — один из самых популярных провайдеров backend-as-a-service (BaaS). BaaS подход позволяет не поднимать свой сервер для хранения и обработки данных приложения. Это используется в мобильных разработках и в обычном вебе. Parse.com имеет свои SDK под несколько платформ, в том числе серверных. Но я расскажу о javascript.

Возможность работать с базой данных через javascript, не поднимая свой сервер, открывает отличные возможности, например, для Single page application (SPA), которое можно хостить на Github Pages, Bitbucket и многих других бесплатных. Первый вопрос, который у меня возник, когда я услышал про работу с БД из клиентского кода — это разграничение прав доступа, так как ключи общеизвестны. Изучив документацию parse.com, я выяснил, что для этого используется авторизация пользователей. Каждый пользователь имеет свой логин и пароль. SDK имеет методы регистрации нового пользователя по логину и паролю, аутентификации по этим же данным. Можно добавить email, при этом сам parse.com умеет отправлять настраиваемые письма для верификации email.

Пример кода с регистрацией
var user = new Parse.User();
user.set("username", "my name");
user.set("password", "my pass");
user.set("email", "email@example.com");
 
// other fields can be set just like with Parse.Object
user.set("phone", "415-392-0202");
 
user.signUp(null, {
  success: function(user) {
    // Hooray! Let them use the app now.
  },
  error: function(user, error) {
    // Show the error message somewhere and let the user try again.
    alert("Error: " + error.code + " " + error.message);
  }
});



Разграничение прав доступа происходит по ACL, которые можно назначать создаваемым объектам. Например, установить для объекта доступ на публичное чтение, но оставив редактирование только авторизованному пользователю. Дополнительно можно устанавливать ограничения на методы по работе с таблицей. Например, можно разрешить всем запись в таблицу, но запретить чтение всем, кроме определенной группы (в случаях логирования, например). Кроме этого, администратору предоставляется мастер-ключ, с помощью которого можно из клиентского приложения получить полный доступ. Но в SPA такое недопустимо, так как ключ не спрятать. Разумеется, есть админка, где можно редактировать схемы, производить CRUD-операции над всеми данными.
Админка:
image

Постановка проблемы


Аутентификация пользователя только по логину и паролю сейчас может выглядеть грубо. Поэтому parse.com имеет в своём SDK класс Parse.FacebookUtils, с помощью которого можно свзязать аккаунты пользователя с соц. сетью. Но это единственная социальная сеть, которую можно использовать через SDK parse.com в javascript. Есть Twitter, но не для javascript. Что делать, если очень хочется аутентифицировать пользователей через другие соц. сети?

Cloud Code


Parse.com, к счастью, предоставляет не только хранилище данных, но и возможность выполнять некоторый код на их сервере. Фичу они назвали Cloud Code. Код создаваемых функций недоступен для пользователей, а значит это можно использовать при построении подписываемых секретным ключом запросах. Но это не особо-то и пригодилось.

Я начал выбирать сервисы, которые имеют готовые виджеты авторизации пользователей на сайтах, и остановился на uLogin. Пару слов о выборе. Когда-то мне попадался на глаза Логинза, но я остался им не доволен, так как там меня просили вводить логин и пароль даже для входа через Твиттер. Возможно, были какие-то другие, соц. приложения которых просили доступ на написание твитов (omg, бесит). Так вот, uLogin имеет хороший набор соц. сетей, готовый виджет, возврат accessToken в callback-фунции для javascript, а также позволяет создавать свои соц. приложения для аутентификации пользователей.

Так как parse.com просит регистрировать пользователей по логину и паролю, то я пришел к выводу, что эту пару можно получить, используя данные, возвращаемые uLogin. Для этого необходимо создать файл clound/main.js в вашем приложении, в котором нужно определить функцию. Назовём её «getCredentials».
Parse.Cloud.define("getCredentials", function(request, response) {
  var token = request.params.token;
  var userLogin, userPassword;

  response.success({'username': userLogin, 'password': userPassword});
});


Сервис uLogin позволяет настроить список полей, которые он будет возвращать. Но среди обязательных есть:
network – идентификатор соцсети пользователя,
profile – адрес профиля пользователя (ссылка на его страницу в соцсети, если удастся ее получить),
uid – уникальный идентификатор пользователя в рамках соцсети,
identity – глобально уникальный идентификатор пользователя.

identity — это, как правило, тоже url, похожий на profile, поэтому я решил склеивать логин пользователя (здесь username) из network и uid.
Для пароля же нужно что-то более секретное, поэтому я решил в качестве пароля использовать хеш от identity, token, secret.
identity для уникальности, token для секретности, т.к. каждый вход в uLogin будет создавать новый токен, известный только пользователю и самому uLogin, и secret — соль, хранимая в настройках приложения.
Этот подход плох тем, что с каждым новом входом через uLogin, будет новый token, а значит и новый пароль. Поэтому нужно каждый раз сохранять новый пароль пользователя. Однако, parse.com после входа пользователя достаточно долго сохраняет сессию, поэтому аутентифицироваться каждый раз не придется. Готовый Cloud Code.

Решение довольно подозрительное по качеству, но в некоторых случаях вполне работоспособно. Например, если отказаться от входа пользователей по их логинам и паролям. Но даже в этом случае, метод можно доработать и перестать обновлять пароль пользователя после получения токена. Как еще один вариант, это использовать в качестве временного пароля случайную строку вместо хеша (осознание этого пришло чуть позже).

Разумеется, временный пароль выдаётся после подтверждения токена, путём получения данных из uLogin. После этого мы определяем, есть ли у нас пользователь с таким логином. Если нет, то регистрируем. Записываем ему данные из соц. сети (псевдоним, аватар) в хранилище, возвращаем пару логин и пароль.
Взаимодействие с uLogin и c parse.com происходит по https, все данные доступны только самому пользователю.

Аутентификация на клиенте
Нужно совсем немного кода, чтобы получить аутентифицированного в parse.com пользователя.
Подключить виджет uLogin
<script src="//ulogin.ru/js/ulogin.js"></script>
<div id="uLogin" data-ulogin="display=small;fields=first_name,last_name;providers=vkontakte,odnoklassniki,mailru,facebook;hidden=other;redirect_uri=&callback=authCallback"></div>

Добавить код callback-функции.
var user;
window.authCallback = function (token) {
        Parse.Cloud.run('getCredentials', {token: token}, {
            success: function (data) {
                Parse.User.logIn(data.username, data.password, {
                    success: function () {
                        user = Parse.User.current();
                    },
                    error: function (user, error) {
                      //handle it
                    }
                });


            }
        });
    };

Здесь происходит вызов написанной нами ранее серверной функции "getCredentials" в Cloud Code.
При следующем заходе пользователя с помощью var user = Parse.User.current(); можно определить, аутентифицирован пользователь или нет. Если да, то виджет uLogin следует скрыть.

Плюсы и минусы одностраничного приложения с использованием parse.com


Плюсы

  • Нет своего сервера, который нужно поддерживать
  • Бесплатно 20GB для файлов, 20GB для данных, 2TB трафика, 30 запросов в секунду, нет ограничения по количеству пользователей
  • Возможность выполнять js-код на сервере
  • Работа с файлами (создание, получение содержимого, получение url)
  • Неплохая готовая аналитика с графиками, событиями и прочее.


Минусы:

  • Задержка получения данных
  • Нет автоматического резервного копирования (есть кнопка для создания дампа)
  • Нет автоматического очищения неиспользуемых файлов (есть кнопка для этого)
  • Необходимость проектирования с целью уменьшения количества запросов к бэкэнду
  • Зависимость от двух внешних сервисов: parse.com и uLogin (вторую зависимость можно легко заменить на свой сервер)


Замечание:

Возможно написание других клиентов к тем же общедоступным данным. Если клиентская сторона позволяет аутентифицировать пользователя через uLogin, то нет никаких преград, чтобы управлять данными пользователя. Недостаток в том, что лимит на количество запросов общий.

Демо-проект


Посмотрев на imhonet, bookmix, livelib у меня появилась мысль сделать свой сервис для хранения прочитанных книг. Главная цель которого — список книг, без лишних довесов вроде оценок, покупок, обложек. При выборе подобных сервисов, интересует надежность хранения данных, чтобы не вышло так, что проект закроется через год-другой, а данные будут утрачены. Поэтому при разработке своего сервиса я решил использовать внешние бесплатные хранилища данных, то есть BaaS. Это позволит не оплачивать хостинг, не беспокоится о проблемах обновления ПО. Статику планировал держать на Github Pages. Но раз сервис стал зависеть от parse.com, то и хостингом решил пользоваться тоже его. Для SPA решил использовать AngularJS, так как был с ним знаком и знал, что мне от него нужно. Знатоки разделения сущностей заметят странные зависимости между ними, но с каждым проектом на AngularJS я узнаю что-то новое о нём. Теперь знаю, как сделать лучше. Переписывать только из академических целей посчитал лишним.

Некоторые могут сказать, что регистрация пользователей только через соц. сети может повлечь серьезные трудности по восстановлению доступа к проекту в случае утраты доступа к соц. сети. Эту проблему можно было бы решить, настроив uLogin так, чтобы он спрашивал email пользователя (и сам верифицировал его), а затем восстанавливать доступ по email. Но в демо-проекте такого нет, так как список прочитанных книг общедоступен, а значит, в случае серьезных проблем, его можно просто продублировать на другую учётную запись.

Исходный код
Демо

P.S.: Демо — это мой продакшн, выключен не будет.

Upd: хабраэффекта не было, до лимита еще много (кликните для увеличения)
image
@getId
карма
20,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (19)

  • +2
    Прекрасная и очень обнадеживающая статья, большое спасибо.

    Планирую заюзать Parse в качестве бэкенда для EmberJS. Имеется плагин ember-parse-adapter, но он недописан (нет поддержки ACL, relationships и прочего) и заброшен. :( Вот думаю, насколько будет сложно воспользоваться Parse в Ember без дополнительных инструментов и SDK, просто по REST.

    UPD: Смотрю, Parse не поддерживает OAuth по REST. Пичалька.
  • +1
    При выборе подобных сервисов, интересует надежность хранения данных, чтобы не вышло так, что проект закроется через год-другой, а данные будут утрачены. Поэтому при разработке своего сервиса я решил использовать внешние бесплатные хранилища данных, то есть BaaS. Это позволит не оплачивать хостинг, не беспокоится о проблемах обновления ПО.


    Делал себе заметочницу для работы, исходил из тех же соображений. Получилось GitHub.io + AnuglarJS + BackendLess. Регистрацию делал по имейлу, чтобы не заморачиваться.
    В BackendLess всё, примерно, то же самое, кроме серверного языка… поэтому там даже лень было пробовать писать свои функции. :)
    JavaScript у Parse.com в этом плане очень привлекателен, скоро опробую. Автору спасибо :)
    • +1
      посылаю вам реквест на сырцы ))) сейчас как раз занимаюсь весьма бесполезной, но важной для собственного развития, задачей по гитхабу ))
      а Автору огромное спасибо! про parse слышал, но благодаря статье — восхищён, и хочу попробовать в ближайшее время )
      • 0
        пожалуйста, угощайтесь — сырцы
        • 0
          Браво! ))
    • 0
      В последней версии BaasBox тоже появилась возможность писать серверсайд код при помощи плагинов.
      По функционалу мало чем уступает parse при том что он opensourсe
      • 0
        Но за бесплатно его же можно только у себя на сервере поднимать? При этом еще и код через плагины.
  • 0
    Может и хорош этот Parse.com, но уж больно дорог. 100 запросов в секунду стоит 700$, а это 2 средних сервера в аренду.

    Подкупает наличие бесплатного акаунта, которого с уверенностью хватит на сайт с посещаемостью около 2к в день, но долго ли он будет бесплатен — тот еще вопрос.
    • 0
      Это, конечно, не то, чего хотелось бы, но с некоторым усилием BaaS можно заменить на другой. Можно даже на свой backend, если другого выхода не будет.
  • 0
    Я извиняюсь, а есть нужен XHR, как обходится cross origin на подобных платформах?
    • 0
      Javascript Parse SDK как раз и общается со своим API через XHR. В заголовках ответа их сервера есть Access-Control-Allow-Origin: *.
      Как решается проблема (и решается ли) для устаревших браузеров, я не выяснял.
      • 0
        Интереснее, как в этом случае решается проблема с авторизацией, так как «Access-Control-Allow-Origin: *» не позволяет использовать куки. Может быть, все-таки, они в этом заголовке отдают значение заголовка запроса Origin?
        • 0
          Можно открыть консоль и увидеть, что Origin в запросах на сервер есть. Также в каждом запросе на сервер передаётся _ApplicationId, _SessionToken. Это вас интересует?
          image
  • –2
    Возможность работать с базой данных через javascript, не поднимая свой сервер, открывает отличные возможности

    Прошу обратить внимание потенциальных пользователей хабрасообщества на полное отсутствие безопасности такого подхода.
    • +1
      За тем лишь исключением, что это будет общедоступная база, с пользователем read-only — тогда это kind of secure.
      • +1
        В посте об этом сказано:
        Каждый пользователь имеет свой логин и пароль. SDK имеет методы регистрации нового пользователя по логину и паролю, аутентификации по этим же данным. <...>
        Разграничение прав доступа происходит по ACL, которые можно назначать создаваемым объектам.

        Таким образом, есть объекты, доступные как на чтение, так и на запись только пользователю-создателю.
        В демо это «Список к прочтению».
  • 0
    Мы тут то же недавно думали о Parse но после изучения вопроса оказалось, что это подходит только в очень простых случаях.
    Очень хорошо, что мы нашли вот такой текст: profi.co/all-the-limits-of-parse/ там по сути описаны все основные ограничения этого решения.
    • 0
      Спасибо за ссылку. Мы изучим её в своей работе над Scorocode. Может и обзор подробный ограничений сделаем, которые мы учли в разработки. Было бы полезно?
      • 0

        Если вы считаете, что вы сделали, что то хорошее то обязательно делитесь этим. :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.