Данный урок, продолжение серии уроков по derbyjs — реактивному фуллстек фреймворку. Читать предыдущие уроки обязательно (первый, второй).

Этот урок будет состоять из двух частей: первая — обзор серверной части дерби-приложения, и вторая — использование модуля derby-auth (обертки над passportjs) для регистрации/авторизации в дерби-приложении с использованием в том числе и социальных сетей.

Часть 1 — серверный код дерби-приложения


Подготовка

В предыдущих примерах для облегчения восприятия информации в качестве серверной части мы использова��и derby-starter. Этот модуль скрывал от нас подробности серверной настройки. Напомню, что на сервере, derby-приложение строится поверх стандартного expressjs-приложения, в частности там происходит настройка базы данных, подключение нужных нам express-овских middlware. Для нашего сегодняшнего примера необходимо более глубоко разобраться с серверной частью — derby-starter мы использовать не будем.

Если кто-то не знаком с expressjs либо плохо знает nodejs — советую замечательный курс Ильи Кантора.

Базовое приложение

В качестве базы для нашего сегодняшнего примера необходимо скопировать мой репозиторий derby-boilerplate. По сути — это минимальное приложение, аналогичное тому, что было у нас в первом примере, но серверная часть теперь не в derby-starter-е, а в самом проекте (так же здесь используется последний 4-й экспресс, и вообще не используется redis — теперь можно и без него). Копируем командой:

git clone https://github.com/derbyparty/derby-boilerplate.git


Давайте исследуем структуру проекта — она уже вполне боевая в отличии от того, что было в предыдущих примерах:

src/
  app/
  server/
styles/
views/
index.js

В папке app будет наше дерби-приложение. Схематично, в папке server будет находится «аналог» derby-starter-а (я действительно взял derby-starter, скопировал его туда и чуть причесал), его содержимое мы разберем подробней.

В папках styles и views — стили и шаблоны соответственно. Файл index.js аналогичен тому, что было в предыдущих примерах — несколько строк, запускающих все это дело.

Серверная часть derby-приложения

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

Посмотрим, что у нас в папке server:
error/
index.js
server.js

В папочку error я вынес обработчик ошибок, его я здесь не буду. Скажу только, что это expressjs-middlware, которое срабатывает, когда для набранного url нет обработчика, либо в процессе работы приложения произошли ошибки. Если хотите разберитесь сами.

Основное предназначение файла index.js — подцепить express (который настраивается в файле server.js) и поднять сервер, по указанному в настройках порту:
Исходный код (практически без изменений взят из derby-starter)
var derby = require('derby');

exports.run = function (app, options, cb) {

  options = options || {};
  var port = options.port || process.env.PORT || 3000;

  derby.run(createServer);

  function createServer() {
    if (typeof app === 'string') app = require(app);

    var expressApp = require('./server.js').setup(app, options);

    var server = require('http').createServer(expressApp);
    server.listen(port, function (err) {
      console.log('%d listening. Go to: http://localhost:%d/', process.pid, port);
      cb && cb(err);
    });
  }
}


Дальше идет самое интересное, настройка expressjs, с добавленным derby. Разбор этого момента — ключ к пониманию того, как вообще работает дерби.

Я использую 4-й экспресс, у него есть несколько отличий от 3-го. Основное — то, что стандартный middlware теперь отсутствуют в дистрибутиве express и их необходимо устанавливать отдельно.

Вот так, примерно выглядел бы этот файл без дерби:

Код server.js, если бы мы делали обычное expressjs-приложение:
var express             = require('express');

// В 4-ом экспрессе все middleware вынесены в отдельные модули
// приходится каждый из них подключать по отдельности
var session             = require('express-session');

// Сессии будем хранить в монге
var MongoStore          = require('connect-mongo')(session);

// Обработчик ошибок - я вынес его в отдельную папочку,
// чтобы не отвекал
var midError            = require('./error');

var MongoClient = require('mongodb').MongoClient;

exports.setup = function setup(app, options) {

  var mongoUrl = process.env.MONGO_URL || process.env.MONGOHQ_URL || 'mongodb://localhost:27017/derby-app';

  // Инициализируем подкючение к БД 
  MongoClient.connect(mongoUrl);

  var expressApp = express()

 if (options && options.static) {
    expressApp.use(require('serve-static')(options.static));
  }

  expressApp.use(require('cookie-parser')());
  expressApp.use(session({
    secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE',
    store: new MongoStore({url: mongoUrl})
  }));

  // Если бы у на были обычные экспрессовские роуты - мы бы положили их СЮДА

  // Маршрут по умолчанию - генерируем 404 ошибку
  expressApp.all('*', function(req, res, next) { next('404: ' + req.url); });

  // Обработчик ошибок
  expressApp.use(midError());

  return expressApp;
}


В двух словах напомню, как это работает. Сначала подключаются модули, потом идет самое важное — подключение экспрессовских middleware (через expressApp.use). По сути эти middlware — это простые функции, которые будут в том же порядке, как зарегистрированы, вызываться для каждого запроса, пришедшего на сервер. Каждый ��з этих middleware может либо ответить на запрос, завершив цепочку обработки (оставшимся middlware управление передано не будет), либо произвести какие-то промежуточные действия с запросом (например, распарсить cookies, по кукам определить сессию и т.д.) и передать управление дальше по цепочке. Порядок подключения middlware очень важен — именно в этой последовательности функции будут вызываться для каждого запроса.

А вот так выглядит дерби-вариант — комментированный код server.js с дерби
// 4-ый экспресс
var express             = require('express');

// В 4-ом экспрессе все middleware вынесены в отдельные модули
// приходится каждый из них подключать по отдельности
var session             = require('express-session');

// Сессии будем хранить в монге
var MongoStore          = require('connect-mongo')(session);

// Обработчик ошибок - я вынес его в отдельную папочку,
// чтобы не отвекал
var midError            = require('./error');

var derby               = require('derby');

// BrowserChannel - аналог socket.io от Гугла - транспорт, используемый
// дерби, для передачи данных из браузеров на сервер

// liveDbMongo - драйвер монги для дерби - умеет реактивно обновлять данные
var racerBrowserChannel = require('racer-browserchannel');
var liveDbMongo         = require('livedb-mongo');

// Подключаем механизм создания бандлов browserify
derby.use(require('racer-bundle'));

exports.setup = function setup(app, options) {

  var mongoUrl = process.env.MONGO_URL || process.env.MONGOHQ_URL || 'mongodb://localhost:27017/derby-app';

  // Инициализируем подкючение к БД (здесь же обычно подключается еще и redis)
  var store = derby.createStore({
    db: liveDbMongo(mongoUrl + '?auto_reconnect', {safe: true})
  });

  var expressApp = express()

  // Здесь приложение отдает свой "бандл"
  // (т.е. здесь обрабатываются запросы к /derby/...)
   expressApp.use(app.scripts(store));

  if (options && options.static) {
    expressApp.use(require('serve-static')(options.static));
  }

  // Здесь в бандл добавляется клиетский скрипт browserchannel,
  // и возвращается middleware, обрабатывающее клиентские сообщения
  // (browserchannel основан на longpooling - т.е. здесь обрабатываются
  // запросы к адресу /channel)
  expressApp.use(racerBrowserChannel(store));

  // В req добавляется метод getModel, позволяющий обычным
  // express-овским котроллерам читать и писать в БД
  // см. createUserId
  expressApp.use(store.modelMiddleware());

  expressApp.use(require('cookie-parser')());
  expressApp.use(session({
    secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE',
    store: new MongoStore({url: mongoUrl})
  }));

  expressApp.use(createUserId);

  // Здесь регистрируем контроллеры дерби-приложения,
  // они будут срабатывать, когда пользователь будет брать страницы
  // с сервера
  expressApp.use(app.router());

  // Если бы у на были обычные экспрессовские роуты - мы бы положили их 
  // СЮДА

  // Маршрут по умолчанию - генерируем 404 ошибку
  expressApp.all('*', function(req, res, next) { next('404: ' + req.url); });

  // Обработчик ошибок
  expressApp.use(midError());

  return expressApp;
}

// Пробрасываем id-юзера из сессии в модель дерби,
// если в сессии id нет - генерим случайное
function createUserId(req, res, next) {
  var model = req.getModel();
  var userId = req.session.userId;
  if (!userId) userId = req.session.userId = model.id();
  model.set('_session.userId', userId);
  next();
}


Потратьте определенное время, чтобы понять все это. По сути основное здесь то, что используется browserify — он нужен для того, чтобы отдавать клиенту в браузер так называемый «бандл» — блок данных, содержащий все javascript-файлы дерби-приложения, а также шаблоны (css-скрипты здесь содержаться не будут). Нужно понимать, что при обычном запросе какой-нибудь страницы сайта клиентом — в браузер сразу бандл не отдается, это было бы слишком накладно. Отдается уже отрендеренная страница со стилями, и с какими-то первоначальными данными. Это логично ведь здесь очень важна скорость. Далее уже сама эта страница подгружает «бандл» — делается запрос по адресу "/derby/:bandle-name".

Я уже говорил, что одной серверной части дерби может соответствовать несколько «дерби-приложений» — по сути для каждого из них будет свой бандл. Это логично. Так мы, например, можем отделить клиентскую часть сайта от админки — чтобы не передавать лишние данные всем подряд. Если так, то нужно будет прописать:

   expressApp.use(clientApp.scripts(store));
   expressApp.use(adminApp.scripts(store));


То же касается и обработки роутов. Если у на будет 2 «дерби-приложения», мы получим вместо:

  expressApp.use(app.router());


��одключение двух роутеров приложений:

  expressApp.use(clientApp.router());
  expressApp.use(adminApp.router());


Ну и еще вы должны понимать то, что кроме обработчиков роутов «дерби-приложений» мы здесь вполне можем добавить своих expressjs обработчиков, организуя любой, необходимый нам RESTFull API, как говорится, с блекджеком и барышнями. Это все работает благодаря тому, что клиентский роутер дерби-приложений, не находя нужного обработчика у себя в приложении, просто отправляет запрос для обработки на сервер.

Следующим важным моментом является подключение browserchannel — это гугловский аналог socket.io. По сути это транспорт, благодаря которому наше приложение в реальном времени будет синхронизировать данные с сервером. Под капотом находится то, что в определенный момент клиентский скрипт browserchannel добавляется в бандл, а так же обработка запросов от этого модуля (он основан на longpooling — поэтому требуется обработка, в данном случае по адресу /channel)

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

Итак, в данный момент вы должны понять, что данная структура позволяет очень гибко добавлять что угодно в наше приложение. Будь то какие-то expressjs-middlware, плагины дерби, отдельные дерби-приложения, экспресс-обработчики — что угодно. Все это будет спокойно жить вместе.

Так, ну и последний момент на который нужно обратить внимание, особенно в свете вопроса авторизации — сессии. Как вы могли заметить сессии в дерби используются вполне себе express-овские. То есть все происходит по классической схеме: для идентификации используется кука (которая будет храниться в браузере клиента), сами сессии хранятся в mongodb в коллекции sessions.

Если заглянуть в функцию createUserId:

function createUserId(req, res, next) {
  var model = req.getModel();
  var userId = req.session.userId;
  if (!userId) userId = req.session.userId = model.id();
  model.set('_session.userId', userId);
  next();
}

Видно, что в ней из сессии извлекается userId, если же он отсутствует, id генерируется случайным образом (model.id()) и записывается и в модель по пути _session.userId (эти данн��е будут сериализованы и переданы на клиент) и в саму сессию.

Представим себе несколько вариантов запросов:

  1. Пользователь первый раз зашел на сайт и запросил главную страницу — на сервере будет сформирована новая кука, новая сессия (с новым случайным userId) — в браузере отрисовывается главная страница и если мы на клиенте заглянем в модель, то по пути '_session.userId' — увидим наш вновь сформированный айдишник. Теперь бродя по страницам нашего приложения (запросов к серверу не будет) — у клиента всегда есть его Id, и если он сделает какие-то запросы к серверу, мы всегда по куке и по сессии сможем его распознать.
  2. Пользователь так и не зарегистрировался и зашел через неделю. Кука у него сохранилась — ему выдастся тот же userId — все нормально.
  3. Пользователь зашел с другого браузера — то же что и в п. 1

Предположим, как бы мы разработали регистрацию/авторизацию (с точки зрения сервера). Допустим в браузере клиент ввел регистрационные данные — дальше они должны попасть на сервер, где мы бы создали запись в коллекции users, даже id придумывать уже не надо, он уже есть.

При авторизации сложнее, клиент зайдя на сайт получит случайный id, потом зайдет на страницу авторизации — введет логин/пароль, передаст это все на сервер, тут сервер поймет, кто это и должен поменять userId в сессии и в _session.userId.

Что бы мы там хранили в этой коллекции. Скорее всего: username, email, passwordHash, может быть какие-то свои статистические данные. Если на нашем сайте была бы авторизация (регистрация) через соц. сети, то здесь же хранились бы ключи соц.сетей. Еще, конечно, пользовательские настройки — их бы тоже было бы неплохо хранить здесь.

Естественно нам бы захотелось, чтобы часть этой коллекции никто бы не смог просмотреть на клиенте. Сейчас с этим нет проблем. Недавно в дерби появились так называемые проекции (возможность подписываясь на коллекцию, подписываться не на весь документ целиком, а только на определенные его поля), но еще пару недель назад — это было невозможно, и модуль derby-auth, который мы будем рассматривать, этого еще не умеет. Там используется разделение пользовательских данных на 2 части: первая — коллекция auths (здесь только приватные данные), вторая users — здесь открытые общедоступные данные. Итак, derby-auth

Часть 2 — авторизация в derby-приложении (модуль derby-auth)


Немного о derby-auth

На данный момент существует 2 пакета — авторизации для дерби. Это derby-auth и derby-passport. Обе они являются обертками над passportjs, (одна из них, по-моему, форк другой). Актуальные версии и той и той предназначены для использования с пятой версией дерби (напомню 6-ая еще в альфе), под 6-ую еще не обновили, но это не мешает нам использовать derby-auth во всех своих проектах на 6-ке (недоступны только шаблоны html форм, но у нас они все-равно кастомные).

Мой выбор в пользу derby-auth только потому, что я с ним работал.

Еще одно ограничение, о котором я уже упоминал, это то, что пользовательские данные в обоих этих проектах разбиты на две части. Общедоступные данные, должны храниться в коллекции users, а закрытые в auths. Недавно в дерби появилась возможность делать «проекции» — то-есть подписываться только на определенные поля коллекции — думаю, когда эти модули обновятся до 6-ой версии дерби — ограничение уйдет.

update
Через несколько дней после написания данной статьи Владимир Махаев (@vmakhaev) разработал офигенный модуль авторизации derby-login по мотивам derby-auth. Он под 0.6, поддерживает проекции, в нем есть уже готовые компоненты для регистрации, входа, смены пароля — короче, используйте только его.

Кстати, не забудьте поблагодарить Владимира, хотя бы звездочкой на гитхабе. Очень полезную вещь он сделал.

Немного о passportjs


PassportJS — это middleware для авторизации под node.js. Это абстрагирующий модуль, позволяющий вашему приложению использовать как обычную авторизацию логином и паролем, так и авторизацию через практически любой сервис, поддерживающий Oauth, Oauth 2 и т.д. авторизацию. Способы авторизации называются здесь стратегиями. Например, авторизация через логин/пароль называется LocalStrategy («локальной стратегией авторизации»), а автризация через GitHub — GithubStrategy. Каждая стратегия вынесена в отдельный модуль, таким образом подключение всего этого дела к node.js-приложению будет выглядеть примерно так:
var passport       = require('passport');
var LocalStrategy  = require('passport-local').Strategy;
var GithubStrategy  = require('passport-github').Strategy;


Здесь же следует отметить особенности использования стратегий авторизации через социальные сети. Если кто-то еще не знает, то прежде, чем наше приложение сможет использовать такую авторизацию, необходимо его (приложение) зарегистрировать в соц. сети и получить два параметра, обчно это appId (или clientId — некий уникальный id-шник нашего приложения в этой соц. сети) и Secret (секретный ключ, который никому нельзя говорить, он будет использоваться для того, чтобы наше приложение смогло подтвердить, что мы — это мы). При регистрации так же необходимо будет ввести так называемый redirectURL — то-есть url-адрес, куда соц. сеть перенаправит браузер клиента после авторизации. В моем примере это будет: localhost:3000/auth/github/callback — но об этом чуть позже.

В приложении-примере, я буду использовать авторизацию через github, регистрация приложения там очень простая (у других провайдеров не сложнее). Прежде всего нужно иметь обычный пользовательский аккаунт, в его настройках легко создается приложение.


Никому не говорите мой секрет.

Подключаем derby-auth к нашему приложению

Итак, прежде всего ставим derby-auth напрямую из git-репозитория, почему-то мастер версия у них под предыдущую derby:

npm install git://github.com/cray0000/derby-auth#0.5 -S

Derby-auth автоматом ставит passport и passport-local, осталось установить passport-github:

npm install passport-github -S


Теперь переходим к настройкам. Все будем делать в соответствии с инструкцией — https://github.com/lefnire/derby-auth/tree/0.5. Большая часть действий по подключению осуществяется на сервере, то есть нам нужен src/server/server.js.

Шаг 1

Подключаем сам derby-auth, и настраиваем стратегии (естественно в нормальном приложении, настройки должны браться из файла-конфига или переменных окружения)

  // Подключаем derby-auth 
  var auth = require('derby-auth');

  var strategies = {
    github: {
      strategy: require("passport-github").Strategy,
      conf: {
        clientID: 'eeb00e8fa12f5119e5e9',
        clientSecret: '61631bdef37fce808334c83f1336320846647115'
      }
    }
  }


Понятно, что если бы стратегий было больше, все они были бы здесь перечислены:

Шаг 2

Устанавливаем опции:

  var options = {
    passport: {
      failureRedirect: '/login',
      successRedirect: '/'
    },
    site: {
      domain: 'http://localhost:3000',
      name: 'Derby-auth example',
      email: 'admin@mysite.com'
    },
    smtp: {
      service: 'Gmail',
      user: 'zag2art@gmail.com',
      pass: 'blahblahblah'
    }
  }

В принципе, все понятно из названий: failureRedirect, successRedirect — url-адресу куда клиент будет перенаправлен в случае не успешной/успешной авторизации соответственно. Информация о сайте нужна для отсылки в стратегии (например, ее могут использовать соц. сети, если соответствующие поля не были заполнены при регистрации приложения). Параметры почты нужны для того, чтобы работала возможность «забыл пароль — сбросьте его и отошлите мне новый на мыло».

Шаг 3

Инициализируем хранилище

  auth.store(store, false, strategies);


На этом шаге derby-auth добавляет контроль доступа к данным, пользователь не сможет получить чужие записи из коллекции auths — будет видеть только свою учетку. Второй параметр в данный момент не используется.

Шаг 4

Убедимся, что к экспрессу подключены middleware по хранению сессий и body-parser (у нас его пока нет — установим npm i body-parser -S и подключим):

  expressApp.use(require('cookie-parser')());
  expressApp.use(session({
    secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE',
    store: new MongoStore({url: mongoUrl})
  }));

  expressApp.use(require('body-parser')())
  // Добавим method-override тоже,
  // я обычно его ставлю, чтобы PUT стал доступен
  // в контроллерах
  expressApp.use(require('method-override')())


Ну и последнее — подключаем derby-auth middeware — здесь подключается куча всего об этом дальше:

expressApp.use(auth.middleware(strategies, options));


Давайте разберемся, что здесь происходит. Если уж это — middleware, логично предположить, что здесь регистрируются какие-то контроллеры expressjs, обрабатывающие запросы к определенным url. Так оно и есть — вот сводная информация о контроллерах:
Роут Метод Параметры Предназначение
/login post username, password Служит для входа в систему по логину и паролю
/register post username, email, password Служит для регистрации пользователя по логину и паролю
/auth/:provider get отсутствуют Переходя сюда мы будем перенаправлены на страницу авторизации соответствующей соц. сети.
Пример пути: /auth/github
/auth/:provider/callback get отсутствуют Сюда соц. сеть сернет управления после авторизации — derby-auth обработает это и перенаправить нас на failureRedirect или successRedirect url.
Пример пу��и: /auth/github/callback — этот url нужен для того, чтобы ввести его в настройках при регистрации приложения в соц. сети.
/logout get отсутствуют Сделав такой запрос мы «выйдем» из аккаунта и будем перенаправлены на '/'
/password-reset post email На соответствующий email будет отослан новый пароль (запрос должен быть AJAX)
/password-change post uid, oldPassword, newPassword Смена пароля соответствующего юзера (запрос должен быть AJAX)


В инструкции есть еще один (необязательный) шаг — подключение компонет (html и стили форм авторизации/регистрации и т.д.), но эти компоненты написаны под дерби 0.5 и еще не обновлены. В своем примере, я следаю простейшую реализацию форм, чтобы продемонстрировать базовые принципы работы. Но прежде необходимо обсудить еще один момент.

Что мы увидем в модели на клиенте


Derby-auth передает определенные данные на клиент, которые позволяют узнать авторизовался ли пользователь, его id, а так же вспомогательную информацию, описывающую проблемы со входом и регистрацией:
Пусть Описание
_session.loggedIn Булева переменная — авторизован/не авторизован пользователь
_session.userId id пользователя
_session.flash.error Массив строк — вспомогательные сообщения об ошибках (всевозможные ошибки авторизации/регистрации)
auth.{userId}.local Данные по локальной регистрации
auth.{userId}.{provider} Данные по регистрации через соответсвующую соц. сеть

Все это доступно в шаблонах и будет нами использовано, для определения статуса авторизации.

Пример приложения

Вообще, конечно, если бы визуальные компоненты из derby-auth подходили бы к 6-ой версии дерби, приложение написалось бы в два счета, без них сложнее. Чтобы упростить разработку, подключим bootstrap, для этого в src/app/index.js вписываем
app.use(require('d-bootstrap'));

предварительно сделав
npm i d-bootstrap -S

Третий бутстрап установлен.

В приложении я сделаю 2 страницы: первая, под адресу "/" со статусом авторизации, чтобы мы могли увидеть вошел ли пользователь в свою учетку, а так же учетные данные, вторая, по адресу '/login' непосредственно с фармами регистрации авторизации, чтобы поэкспериментировать. Так же перед рендерингом каждой из этих двух страниц, я буду подтягивать учетные пользовательские данные из коллекции auths.

Думаю файл дерби-приложения src/app/index.js не требует особых комментариев, единственное, что мы не обсуждали, это контроллер"*", но это стандартная штука для экспресса:

var derby = require('derby');
var app = module.exports = derby.createApp('auth', __filename);

global.app = app;

app.use(require('d-bootstrap'));


app.loadViews (__dirname+'/../../views');
app.loadStyles(__dirname+'/../../styles');

app.get('*', function(page, model, params, next){

  var user = 'auths.' + model.get('_session.userId');
  model.subscribe(user, function(){
    model.ref('_page.user', user);
    next();
  });

});

app.get('/', function (page, model){
  page.render('home');
});

app.get('/login', function (page, model){
  page.render('login');
});



По шаблонам поступим так — сделаем layout c менюшкой в файле index.html, а в файлы home.html и login.html положим, соответственно, страницу с информацией о статусе пользователя (вошел/не вошел, привязан ли github-аккаунт), и страницу с авторизацией/регистрацией.

Итак, index.html
<import: src="./home">
<import: src="./login">


<Title:>
  Derby-auth example

<Body:>
  <div class="navbar navbar-inverse" role="navigation">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="/">Derby-auth Example</a>
      </div>
      <div class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
          <view name="nav-link" href="/">Информация</view>
          <view name="nav-link" href="/login">Логин</view>
        </ul>
      </div>
    </div>
  </div>


  {{if _session.flash.error}}
    <div class="container">
      {{each _session.flash.error as #error}}
        <div class="alert alert-warning">{{#error}}</div>
      {{/}}
    </div>
  {{/}}


  <view name="{{$render.ns}}"></view>

<nav-link: element="nav-link">
  <li class="{{if $render.url === @href}}active{{/}}">
    <a href="{{@href}}">{{@content}}</a>
  </li>



Эта менюшка и вообще большая часть разметки здесь — чисто вспомогательные и особо не связаны с авторизацией как таковой (ну кроме alert-ов). Единственное, что здесь может вызвать вопросы, это:

<view name="{{$render.ns}}"></view>


Вместо этой строки дерби будет вставлять содержимое либо home.html, либо login.html, в зависимости от того, что мы прописали в контроллере: page.render('home') или page.render('login')

Страничка со вспомогательной информацией, home.html:

<index:>
  <div class="container">

    <h1>Информация:</h1>
    {{if _session.loggedIn}}
      <p>Пользователь вошел в систему</p>


      <h2>Локальная стратегия:</h2>
      {{if _page.user.local}}
        <p>Имя пользователя: <b>{{_page.user.local.username}}</b></p>
      {{else}}
        <p>Данные отсутствуют</p>
      {{/}}

      <h2>GitHub:</h2>
      {{if _page.user.github}}
        <p>Имя пользователя: <b>{{_page.user.github.username}}</b></p>
      {{else}}
        <p>Данные отсутствуют</p>
      {{/}}

      <a class="btn btn-danger" href="/logout">Выйти</a>
    {{else}}
      <p>Пользователь НЕ вошел в систему</p>
      Старница <a href="/login">Регистрации/Авторизации</a>
    {{/}}
  </div>


Обратите внимание на кнопку «Выйти» — здесь используется контроллер GET /logout из derby-auth

Не будем особо заморачиваться и возьмет форму логина вместе со стилями прямиком с сайта бутстрапа, там есть отличный пример «sign in» http://getbootstrap.com/examples/signin/

Стили тупо скопрированы из бутстраповского примера - файл slyles/index.styl
.form-signin {
  max-width: 330px;
  padding: 15px;
  margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
  margin-bottom: 10px;
}
.form-signin .checkbox {
  font-weight: normal;
}
.form-signin .form-control {
  position: relative;
  height: auto;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  padding: 10px;
  font-size: 16px;
}
.form-signin .form-control:focus {
  z-index: 2;
}
.form-signin input[type="email"] {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}



Файл login.html с авторизацией и регистрацией:

<index:>

  <view name="signin"></view>
  <hr/>
  <view name="signup"></view>

<signin:>
  <div class="container">
    <form class="form-signin" role="form" action='/login' method='post'>
      <h3 class="form-signin-heading">Вход</h3>
      <input name="username" type="text" class="form-control" placeholder="имя" required="" autofocus="">
      <input name="password" type="password" class="form-control" placeholder="пароль" required="">

      <button class="btn btn-lg btn-primary btn-block" type="submit">Войти</button>
      <br/>
      <a class="btn btn-lg btn-danger btn-block" href="/auth/github">Через GitHub</a>
    </form>
  </div>

<signup:>
  <div class="container">
    <form class="form-signin" role="form" action='/register' method='post'>
      <h3 class="form-signin-heading">Регистрация</h3>
      <input name="username" type="text" class="form-control" placeholder="имя" required="" autofocus="">
      <input name="email" type="email" class="form-control" placeholder="имеил" required="">
      <input name="password" type="password" class="form-control" placeholder="пароль" required="">

      <button class="btn btn-lg btn-primary btn-block" type="submit">Зарегистрироваться</button>
    </form>
  </div>


Если запустить все будет выглядеть примерно вот-так:


Бутстаповскую форму я немножко поменял, исходя из наших derby-auth контролленов. На что обращаем внимание в форме регистрации: это post форма ведущая к экшену /login — обрабатывать будет derby-auth, имена полей username и password — все из нашей таблички. Вход через GitHub — get запрос к /auth/github — тоже оттуда.

Форма регистрации. То же самое — post к /register, поля: username, email, password.

Запускаем приложение: npm start — смотрим статус — «пользователь не вошел в систему», регистрируемся, видим — «пользователь в системе». Можем выдти — /logout. Пока не вышли у нас есть возможность привязать к текущей учетке github-аккаунт, для этого нужно кликнуть «войти через github», мы будем перенаправлены на гитхаб, там авторизуемся, все подтвердим и вернемся обратно на сайт.

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

Поиграйтесь с тем, что получислось. Просмотрите доступные данные, используя консоль разработчика (введите app.model.get() в консоли).

Прошу прощения, что мы не реализовали в примере оставшиеся фишки derby-auth (сброс и изменение пароля) — этот урок и так вышел довольно большим из-за необходимости объяснять серверную часть дерби.

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

Итоговый вариант на github

Прошу тех, кто хоть что-то пробовал сам, отписаться в комментариях и рассказать, что получилось. Не можете писать комментарии на храбре — пишите на мыло. Интересно же, не зря я вообще пишу.

P. S.
Если не хотите пропустить следующие статьи по derbyjs, подписывайтесь на обновления в моем профиле: zag2art. Сам так делаю — на хабре же нет возможности добавить в трекер определенный (очень интересный) хаб, чтобы точно ничего не пропустить.

P.P.S.
Кстати, уже вышла дерби 0.6 alpha 7 — окончательный релиз все ближе и ближе.

Если нравится derbyjs — не сочтите за труд поставить звездочку на github
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Ну так как, продолжать серию?
92.86%Да130
7.14%Нет10
Проголосовали 140 пользователей. Воздержались 17 пользователей.