Аутентификация через социальные сети с AngularJS и hello.js


Существует ряд решений для задачи аутентификации через социальные сети. Например, можно использовать PassportJS. PassportJS дает большую гибкость и кучу возможностей и тд, но мне показалось что все должно решаться гораздо проще и хотелось бы по-меньше использовать бэкенд. Забегая вперед, отмечу что без бэкенда вовсе, к сожалению, не получится обойтись.

И так, будем использовать hello.js для решения поставленной задачи.

Hello.js это клиентский JavaScript SDK для аутентификации через сервисы, использующие oAuth2 и oAuth, например twitter, github, facebook и тд.

Мы будем использовать только twitter, поэтому сначала нужно создать приложение, которое будет получать доступ к данным пользователя. Как это сделать можно узнать тут.

Для провайдеров, которые используют oAuth или oAuth2 с Explicit Grant, в процессе аутентификации должен присутствовать secret key, а тк библиотека клиентская, не очень хорошо хранить ключ на клиенте и передавать его в открытом виде. Об этой проблеме уже позаботились за нас и hello.js дает возможность использовать промежуточный сервис через прокси. Этот сервис будет получать secret key и реализовывать handshake механизм для получения access_token. Подробнее тут.

Кстати сказать, можно не писать свой сервис для этого, а использовать прокси, который автор hello.js предоставляет для этих целей. Но это означает, что нужно будет засветить свой secret key третьему лицу, плюс, не очень хорошо полагаться на сторонний сервис (который, как минимум, может быть недоступен), разве что наша цель просто “поиграться” с библиотекой и забыть..

И так..

Дальше можно не читать, а просто забрать исходники приложения тут

На клиенте у нас будет AngularJS и hello.js, на бэкенде Express + oauth-shim. Соберем все gulp‘ом.

Так будет выглядеть структура нашего проекта:

project_structure

package.json для установки зависимостей для серверной части и консольных скриптов:

bower.json для установки зависимостей для клиентской части:

gulpfile.js будет просто собирать js файлы в один, минифицировать и копировать в /public/js:

Сразу рассмотрим код серверной части, он не так интересен. Финальный вариант:

Из интересного тут только oauth-shim, остальное нужно чтобы просто работало приложение

Подключаем

1 oauthshim = require('oauth-shim')

Инициализируем (тут мы вписываем clientId и clientSecret из настроек нашего твиттер приложения)

1 oauthshim.init({
2   'clientID' : 'clientSecret'
3 });

Вызываем (Все запросы от клиента к прокси будут вызывать oauthshim)

1 app
2 .get('/proxy', oauthshim.request)

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

Шаблон директивы делится на две части. Если пользователь не авторизирован, показываем ему кнопку аутентификации, иначе - кнопку выхода. Шаблон меняется в зависимости от значения переменной isAuthorized в скоупе директивы:

index.html нашего приложения:

Первый запуск:

в консоле переходим в корень нашего приложения и выполняем команды:

Установка серверных зависимостей

npm install

Установка клиентских зависимостей

bower install

Сборка клиента

gulp

Все три утилиты должны быть предварительно установленны глобально в системе.

Дальше запустим сервер и посмотрим что получилось:

npm start

Переходим в браузере по адресу localhost:8080 и, если все предыдущие действия прошли успешно, видим кнопку авторизации:

App

Разберем код app.js и workflow нашего приложение.

Сценарий первый (пользователь не авторизован):

Пользователь кликает по кнопке, срабатывает метод authCtrl.login('twitter'), который вызывает функцию login фабрики, отвечающую за аутентификацию и передает в нее имя провайдера (в нашем случае это twitter)

1 authCtrl.login = function(provider){
2   AuthFactory.login(provider);
3 }

Далее эта функция инициализирует библиотеку hello.js и вызывает метод ответственный за аутентификацию. Когда аутентификация завершится, вернем response обратно в контроллер:

1 function login(provider){
2   init();
3   hello(provider).login().then(function(response) {
4     $rootScope.$broadcast('auth', response);
5   });
6 }

В функции init указываем адрес прокси и адрес переадресации после аутентификации:

 1 function init(){
 2   if(!isInited){
 3     hello.init(
 4     {twitter : 'twitter_client_id_here'},
 5     {
 6       redirect_uri: '/redirect',
 7       oauth_proxy: '/proxy'
 8     });
 9     isInited = !isInited;
10   }
11 }

Далее прокси получает запрос и обрабатывает его

1 app
2 .get('/proxy', oauthshim.request)

Происходит handshake и мы получаем access_token, после чего происходит редирект

1 .get('/redirect', function(req, res){
2   res.sendFile('close.html', {root: app.get('views')});
3 })

В файле close.html просто подключается наш js файл. Hello.js сам закроет окно после аутентификации

После успешной аутентификации, метод hello(provider).login() вернет нам ответ с информацией об аутентификации:

response

Далее мы получаем response в методе link директивы, если получен access_token, то переключаем переменную isAuthorized в true и узнаем имя пользователя, для отображения в шаблоне:

1 scope.$on('auth', function(event, response) {
2   if(response.authResponse.access_token){
3     scope.authCtrl.getUserDetails();
4     scope.$apply(function(){
5       scope.isAuthorized = true;
6     });
7   }
8 });
1 vm.getUserDetails = function() {
2   var authData = AuthFactory.getAuthResponse('twitter');
3   if(authData){
4     vm.userName = authData.screen_name;
5     $scope.isAuthorized = true;
6   }
7 };

Поскольку теперь isAuthorized === true в шаблоне будет отображена кнопка выхода, вместо кнопки аутентификации:

logged_in

Сценарий второй (пользователь авторизован):

Пользователь заходит на страницу, в методе link мы проверяем есть ли данные о пользователе, если есть - получаем имя пользователя, переключаем переменную isAuthorized в true. Поскольку isAuthorized === true в шаблоне будет отображена кнопка выхода, вместо кнопки аутентификации.

1 function link(scope, element, attrs) {
2   scope.authCtrl.getUserDetails();
3   ...
4 }
1 vm.getUserDetails = function() {
2   var authData = AuthFactory.getAuthResponse('twitter');
3   if(authData){
4     vm.userName = authData.screen_name;
5     $scope.isAuthorized = true;
6   }
7 };

Пользователь кликает на кнопку logout, вызывается соответствующий метод контроллера директивы, который вызывает метод фабрики, ответственный за logout. hello.js удаляет данные о пользователе и мы переключаем переменную isAuthorized в false:

1 vm.logout = function(provider) {
2   AuthFactory.logout(provider);
3   $scope.isAuthorized = false;
4 };

Поскольку isAuthorized === false в шаблоне будет отображена кнопка входа, вместо кнопки выхода.

Кстати сказать, hello.js использует LocalStorage для хранения данных.

Исходники этого приложения можно забрать тут.