Мне было сложно найти пример, демонстрирующий, как реализовать социальную аутентификацию при использовании RESTful API, поэтому я решил, что напишу об этом, чтобы избавить вас от опасности (это действительно не так уж и плохо).

Мы собираемся реализовать аутентификацию Facebook, Google и Twitter, используя интерфейс React и серверную часть Node.

Если все, что вам нужно, это аутентификация в Twitter, просто следуйте этому замечательному руководству: https://medium.com/@robince885/how-to-do-twitter-authentication-with-react-and-restful-api-e525f30c62bb.

Если вы хотите увидеть пример с интерфейсом Angular, посмотрите этот: https://github.com/GenFirst/angular2-node-fb-login

Суть

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

Шаг 1. В интерфейсе отобразите всплывающее окно входа в стороннее приложение.

Шаг 2. (Все еще в интерфейсе) Возьмите токен аутентификации, который стороннее приложение возвращает после согласия на вход.

Шаг 3. (Да, все еще интерфейс) Отправьте этот токен в серверную часть как часть тела вашего запроса. (Мне нравится использовать Blobs, но это только я.)

Шаг 4. Проверьте токен на сервере.

Шаг 5. Если токен является подлинным, вы получите пользователя как часть проверочного ответа (по крайней мере, так обстоит дело с Passport.js, который мы будем использовать).

Шаг 6. Сохраните данные пользователя в своей базе данных.

Шаг 7. Верните токен JWT во внешний интерфейс. То, что вы делаете с этим токеном, выходит за рамки этого руководства, но его, вероятно, следует использовать для аутентификации каждого действия вошедшего в систему пользователя.

Вот и все, скелет для создания входа в соцсети.

Кстати, пока я не забыл, правильная обработка этого токена JWT - нетривиальная проблема. Поскольку вы не используете Rails, в котором предусмотрены меры безопасности для вещей, о которых вы не подозревали, вам придется разработать процедуры, чтобы хотя бы избежать:

CSRF-атаки: злоумышленник хватается за ваш токен JWT и притворяется вами.

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

Хорошо, разглагольствуй.

Давай катимся

Мы реализуем аутентификацию Facebook, Google и Twitter одновременно, поэтому нам не нужно постоянно повторять наши шаги. Давайте добавим каталог, который будет домом для внешнего и внутреннего интерфейса. Выполните следующее:

mkdir social-auth-example && cd social-auth-example

Давайте воспользуемся приложением create-react-app для нашего интерфейса.

Выполните следующее, если у вас еще нет глобального модуля:

npm i -g create-react-app

а затем запустить

create-react-app frontend && cd frontend

Вы можете дважды проверить, что все установлено правильно, выполнив следующее:

npm start

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

npm i --save react-scripts

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

Выполните следующее:

npm i --save react-twitter-auth react-facebook-login react-google-login

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

import React, { Component } from 'react';
import TwitterLogin from 'react-twitter-auth';
import FacebookLogin from 'react-facebook-login';
import { GoogleLogin } from 'react-google-login';

class App extends Component {

    constructor() {
        super();
        this.state = { isAuthenticated: false, user: null, token: ''};
    }

    logout = () => {
        this.setState({isAuthenticated: false, token: '', user: null})
    };
    
    twitterResponse = (e) => {};

    facebookResponse = (e) => {};

    googleResponse = (e) => {};
    onFailure = (error) => {
      alert(error);
    }
    render() {
        let content = !!this.state.isAuthenticated ?
            (
                <div>
                    <p>Authenticated</p>
                    <div>
                        {this.state.user.email}
                    </div>
                    <div>
                        <button onClick={this.logout} className="button">
                            Log out
                        </button>
                    </div>
                </div>
            ) :
            (
                <div>
                    <TwitterLogin loginUrl="http://localhost:4000/api/v1/auth/twitter"
                                   onFailure={this.twitterResponse} onSuccess={this.twitterResponse}
                                   requestTokenUrl="http://localhost:4000/api/v1/auth/twitter/reverse"/>
                    <FacebookLogin
                        appId="XXXXXXXXXX"
                        autoLoad={false}
                        fields="name,email,picture"
                        callback={this.facebookResponse} />
                    <GoogleLogin
                        clientId="XXXXXXXXXX"
                        buttonText="Login"
                        onSuccess={this.googleResponse}
                        onFailure={this.googleResponse}
                    />
                </div>
            );

        return (
            <div className="App">
                {content}
            </div>
        );
    }
}

export default App;

Если вы зайдете на страницу сейчас, вы увидите три ужасно выглядящие кнопки. Если приложение не удалось скомпилировать, возможно, решением будет повторная установка node_modules:

npm i

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

Примечание. В реальном приложении вы, вероятно, захотите хранить эти данные в системе хранения, такой как Redis или LocalStorage, которая сохраняется между сеансами.

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

Хорошо, могу немного помочь:

Твиттер

Для Twitter вы можете получить эту информацию здесь: https://apps.twitter.com

Шаг 1. Нажмите кнопку Создать новое приложение.

Шаг 2: перейдите к разрешениям и установите флажок «Запрашивать электронные письма от пользователей». Нам понадобятся адреса электронной почты.

Шаг 3. Скопируйте значения Consumer Key и Consumer Secrets в какое-нибудь частное место.

Шаг 4. На вкладке Настройки заполните обязательные поля. Если вы создали учетную запись Github для этого руководства, используйте ее для веб-сайта. В противном случае просто используйте репозиторий этого руководства: https://github.com/alien35/social-auth-example

Google

Шаг 1: перейдите в консоль разработчика: https://console.developers.google.com/.

Шаг 2. Найдите в строке поиска «учетные данные oauth» и выберите один из всплывающих окон.

Шаг 3: попробуйте найти кнопку "Создать учетные данные". Если вы его найдете, нажмите на него. Выберите «Идентификатор клиента Oauth» (рисунок 1).

В качестве типа приложения выберите веб-приложение. Авторизованные источники и URL-адреса перенаправления немного сбивают с толку, поэтому я просто скопировал и вставил кучу различных вариантов (рисунок 2), которые работали для меня в прошлом. Что касается производственных настроек, вы, вероятно, захотите быть более конкретными.

Шаг 4. Нажмите "Сохранить" и скопируйте значения Client Id и Client Secret, которые скрываются где-то на той же странице.

Facebook

Шаг 1: перейдите на https://developers.facebook.com/apps/ и выберите Добавить новое приложение.

Шаг 2. Дайте своему приложению имя и ответьте на секретный вопрос.

Шаг 3. Если вы видите вариант выбора продуктов, выберите «Вход в Facebook».

Шаг 3: перейдите в Настройки и в разделе Домены приложений введите localhost.

Шаг 4: нажмите Вход в Facebook в разделе Продукты на боковой панели и добавьте следующий URL-адрес перенаправления: http: // localhost: 3000 / api / auth / facebook / callback

Это должно сработать. Если вы столкнетесь с проблемами URI перенаправления при попытке использовать социальный вход, вы определенно не первый, и вы найдете множество документации в Интернете (но не больше в этом руководстве).

Из корня внешнего интерфейса давайте запустим команду для создания файла конфигурации для наших вновь созданных учетных данных:

touch src/config.json

Добавьте в этот файл что-то вроде следующего:

{
  "GOOGLE_CLIENT_ID": "XXXXX",
  "FACEBOOK_APP_ID": "XXXXX"
}

Замените X на свой идентификатор клиента Google и идентификатор приложения Facebook. Если хотите, добавьте этот файл в свой файл .gitignore. Тем не менее, эти данные общедоступны, поэтому, что бы вы ни делали, не добавляйте свои секретные учетные данные в файлы.

Давайте использовать эти значения в App.js. Теперь этот файл должен выглядеть примерно так:

import React, { Component } from 'react';
import TwitterLogin from 'react-twitter-auth';
import FacebookLogin from 'react-facebook-login';
import { GoogleLogin } from 'react-google-login';
import config from './config.json';

class App extends Component {

    constructor() {
        super();
        this.state = { isAuthenticated: false, user: null, token:                ''};
    }

    logout = () => {
        this.setState({isAuthenticated: false, token: '', user:    null})
    };
    onFailure = (error) => {
      alert(error);
    };
    twitterResponse = (response) => {};

    facebookResponse = (response) => {
        console.log(response);
    };

    googleResponse = (response) => {
        console.log(response);
    };

    render() {
        let content = !!this.state.isAuthenticated ?
            (
                <div>
                    <p>Authenticated</p>
                    <div>
                        {this.state.user.email}
                    </div>
                    <div>
                        <button onClick={this.logout} className="button">
                            Log out
                        </button>
                    </div>
                </div>
            ) :
            (
                <div>
                    <TwitterLogin loginUrl="http://localhost:4000/api/v1/auth/twitter"
                                   onFailure={this.onFailure} onSuccess={this.twitterResponse}
                                   requestTokenUrl="http://localhost:4000/api/v1/auth/twitter/reverse"/>
                    <FacebookLogin
                        appId={config.FACEBOOK_APP_ID}
                        autoLoad={false}
                        fields="name,email,picture"
                        callback={this.facebookResponse} />
                    <GoogleLogin
                        clientId={config.GOOGLE_CLIENT_ID}
                        buttonText="Login"
                        onSuccess={this.googleResponse}
                        onFailure={this.onFailure}
                    />
                </div>
            );

        return (
            <div className="App">
                {content}
            </div>
        );
    }
}

export default App;

Нажмите кнопки Google и Facebook и проверьте, нет ли ответов на своей консоли. Надеюсь, вы увидите набор данных, включая значение authToken. Как вы могли заметить, аутентификация в Twitter имеет несколько иной процесс. В то время как для Google и Facebook мы запрашиваем токен во внешнем интерфейсе и проверяем его в серверном интерфейсе, для Twitter мы запрашиваем токен и проверяем его в серверной части. Мы делаем это только из-за того, как была спроектирована эта кнопка.

ВОТ ЗАДАЧА: если вы хотите написать кнопку с открытым исходным кодом для ответа на твиттер, которая получает authToken во внешнем интерфейсе на основе ключа пользователя Twitter, я с радостью воспользуюсь этим.

Позвольте мне дать вам остальную логику внешнего интерфейса:

import React, { Component } from 'react';
import TwitterLogin from 'react-twitter-auth';
import FacebookLogin from 'react-facebook-login';
import { GoogleLogin } from 'react-google-login';
import config from './config.json';

class App extends Component {

    constructor() {
        super();
        this.state = { isAuthenticated: false, user: null, token: ''};
    }

    logout = () => {
        this.setState({isAuthenticated: false, token: '', user: null})
    };

    onFailure = (error) => {
        alert(error);
    };

    twitterResponse = (response) => {
        const token = response.headers.get('x-auth-token');
        response.json().then(user => {
            if (token) {
                this.setState({isAuthenticated: true, user, token});
            }
        });
    };

    facebookResponse = (response) => {
        const tokenBlob = new Blob([JSON.stringify({access_token: response.accessToken}, null, 2)], {type : 'application/json'});
        const options = {
            method: 'POST',
            body: tokenBlob,
            mode: 'cors',
            cache: 'default'
        };
        fetch('http://localhost:4000/api/v1/auth/facebook', options).then(r => {
            const token = r.headers.get('x-auth-token');
            r.json().then(user => {
                if (token) {
                    this.setState({isAuthenticated: true, user, token})
                }
            });
        })
    };

    googleResponse = (response) => {
        const tokenBlob = new Blob([JSON.stringify({access_token: response.accessToken}, null, 2)], {type : 'application/json'});
        const options = {
            method: 'POST',
            body: tokenBlob,
            mode: 'cors',
            cache: 'default'
        };
        fetch('http://localhost:4000/api/v1/auth/google', options).then(r => {
            const token = r.headers.get('x-auth-token');
            r.json().then(user => {
                if (token) {
                    this.setState({isAuthenticated: true, user, token})
                }
            });
        })
    };

    render() {
    let content = !!this.state.isAuthenticated ?
            (
                <div>
                    <p>Authenticated</p>
                    <div>
                        {this.state.user.email}
                    </div>
                    <div>
                        <button onClick={this.logout} className="button">
                            Log out
                        </button>
                    </div>
                </div>
            ) :
            (
                <div>
                    <TwitterLogin loginUrl="http://localhost:4000/api/v1/auth/twitter"
                                   onFailure={this.onFailure} onSuccess={this.twitterResponse}
                                   requestTokenUrl="http://localhost:4000/api/v1/auth/twitter/reverse"/>
                    <FacebookLogin
                        appId={config.FACEBOOK_APP_ID}
                        autoLoad={false}
                        fields="name,email,picture"
                        callback={this.facebookResponse} />
                    <GoogleLogin
                        clientId={config.GOOGLE_CLIENT_ID}
                        buttonText="Login"
                        onSuccess={this.googleResponse}
                        onFailure={this.onFailure}
                    />
                </div>
            );

        return (
            <div className="App">
                {content}
            </div>
        );
    }
}

export default App;

Для Facebook и Google, основываясь на ответе всплывающего окна входа в систему, мы отправим токен доступа как часть тела нашего HTTP-запроса на бэкэнд. Бэкэнд сделает с ним что-нибудь (подсказка: проверьте токен, а затем отправьте обратно пользователя, если он действителен + токен JWT). Мы используем тип данных blob, потому что я думаю, что это круто, но не стесняйтесь использовать любой принятый тип данных.

При успешном ответе серверной части мы установим для isAuthenticated значение true, отобразим электронное письмо на экране и сохраним токен JWT. Для Twitter серверная часть будет прослушивать как http: // localhost: 4000 / api / v1 / auth / twitter, так и http: // localhost: 4000 / api / v1 / auth / twitter / reverse . На основе окончательного ответа мы также сможем обработать электронное письмо и сохранить предоставленный серверной частью токен JWT во внешнем интерфейсе.

Бэкэнд

Перейдите в корень приложения и создайте внутреннюю папку:

mkdir backend && cd backend

Давайте запустим экспресс-приложение. Выполните следующее, чтобы получить глобальный экспресс-генератор:

npm i express-generator -g

Теперь убедитесь, что вы находитесь в папке / backend, и выполните следующее:

express && npm i

Давайте немного очистим сервер. Выполните следующее:

npm i --save express-server utils

а затем измените bin / www на следующий

#!/usr/bin/env node

var app = require('../app');
var debug = require('debug')('backend:server');
var http = require('http');

var port = normalizePort(process.env.PORT || '4000');
app.set('port', port);

var server = http.createServer(app);

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

var expressServerUtils = require('express-server-utils')(server, port);
expressServerUtils.listen();
expressServerUtils.handleOnError();
expressServerUtils.handleOnListening();

const exitActions = [server.close];
expressServerUtils.handleShutDown(exitActions);

Хорошо, запустите свой сервер:

npm start

Надеюсь, вы не получаете ошибок. Если вы перейдете по адресу http: // localhost: 4000, вы увидите сообщение Добро пожаловать в экспресс.

Приготовьтесь

Теперь нам нужно проделать утомительную настройку.

  1. Нам нужно настроить Passport и подключить его к нашим маршрутам аутентификации, чтобы проверить наши токены с внешнего интерфейса.
  2. Нам также нужно будет настроить Mongo для хранения наших пользователей.

Я слышу, как вы думаете: Это не еще один пример Mongo! Что ж, чтобы правильно использовать реляционную базу данных, вам, вероятно, придется как минимум настроить миграцию и систему ORM, что сделает это руководство излишне длиннее. После этого урока я бы рекомендовал проверить этот урок: https://medium.com/@alexanderleon/creating-a-scalable-api-using-node-graphql-mysql-and-knex-710a1a475ff4

Отсюда вы, вероятно, сможете понять, как заменить то, что мы делаем в Mongo, на MySQL.

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

mkdir utils && touch passport.js mongoose.js utils2/token.utils.js && npm i --save passport passport-twitter-token passport-facebook-token passport-google-token mongoose jsonwebtoken cors request

С помощью этого скрипта мы настраиваем наш скелет, устанавливаем библиотеки проверки токенов, устанавливаем инструменты для создания токенов JWT, устанавливаем CORS для обеспечения связи между внешним и внутренним приложениями и устанавливаем request, который мы будем использовать. чтобы отправить дополнительный HTTP-запрос в Twitter.

Замените /app.js следующим:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var cors = require('cors');

var index = require('./routes/index');

var app = express();

var corsOption = {
    origin: true,
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    credentials: true,
    exposedHeaders: ['x-auth-token']
};
app.use(cors(corsOption));

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/api/v1/', index);

module.exports = app;

Замените /utils/token.utils.js следующим:

var createToken = function(auth) {
    return jwt.sign({
            id: auth.id
        }, 'my-secret',
        {
            expiresIn: 60 * 120
        });
};

module.exports = {
  generateToken: function(req, res, next) {
      req.token = createToken(req.auth);
      return next();
  },
  sendToken: function(req, res) {
      res.setHeader('x-auth-token', req.token);
      return res.status(200).send(JSON.stringify(req.user));
  }
};

Замените /mongoose.js следующим:

'use strict';

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

module.exports = function () {

    var db = mongoose.connect('mongodb://localhost:27017/social-auth-example');

    var UserSchema = new Schema({
        email: {
            type: String, required: true,
            trim: true, unique: true,
            match: /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/
        },
        facebookProvider: {
            type: {
                id: String,
                token: String
            },
            select: false
        },
        twitterProvider: {
            type: {
                id: String,
                token: String
            },
            select: false
        },
        googleProvider: {
            type: {
                id: String,
                token: String
            },
            select: false
        }
    });

    UserSchema.set('toJSON', {getters: true, virtuals: true});

    UserSchema.statics.upsertTwitterUser = function(token, tokenSecret, profile, cb) {
        var that = this;
        return this.findOne({
            'twitterProvider.id': profile.id
        }, function(err, user) {
            // no user was found, lets create a new one
            if (!user) {
                var newUser = new that({
                    email: profile.emails[0].value,
                    twitterProvider: {
                        id: profile.id,
                        token: token,
                        tokenSecret: tokenSecret
                    }
                });

                newUser.save(function(error, savedUser) {
                    if (error) {
                        console.log(error);
                    }
                    return cb(error, savedUser);
                });
            } else {
                return cb(err, user);
            }
        });
    };

    UserSchema.statics.upsertFbUser = function(accessToken, refreshToken, profile, cb) {
        var that = this;
        return this.findOne({
            'facebookProvider.id': profile.id
        }, function(err, user) {
            // no user was found, lets create a new one
            if (!user) {
                var newUser = new that({
                    fullName: profile.displayName,
                    email: profile.emails[0].value,
                    facebookProvider: {
                        id: profile.id,
                        token: accessToken
                    }
                });

                newUser.save(function(error, savedUser) {
                    if (error) {
                        console.log(error);
                    }
                    return cb(error, savedUser);
                });
            } else {
                return cb(err, user);
            }
        });
    };

    UserSchema.statics.upsertGoogleUser = function(accessToken, refreshToken, profile, cb) {
        var that = this;
        return this.findOne({
            'googleProvider.id': profile.id
        }, function(err, user) {
            // no user was found, lets create a new one
            if (!user) {
                var newUser = new that({
                    fullName: profile.displayName,
                    email: profile.emails[0].value,
                    googleProvider: {
                        id: profile.id,
                        token: accessToken
                    }
                });

                newUser.save(function(error, savedUser) {
                    if (error) {
                        console.log(error);
                    }
                    return cb(error, savedUser);
                });
            } else {
                return cb(err, user);
            }
        });
    };

    mongoose.model('User', UserSchema);

    return db;
};

Замените /config.js следующим (добавьте свои ключи и секреты):

module.exports = {
    'facebookAuth' : {
        'clientID'      : 'your-clientID-here',
        'clientSecret'  : 'your-client-secret-here',
        'callbackURL'     : 'http://localhost:4000/api/auth/facebook/callback',
        'profileURL': 'https://graph.facebook.com/v2.5/me?fields=first_name,last_name,email'

    },

    'twitterAuth' : {
        'consumerKey'        : 'your-consumer-key-here',
        'consumerSecret'     : 'your-client-secret-here',
        'callbackURL'        : 'http://localhost:4000/auth/twitter/callback'
    },

    'googleAuth' : {
        'clientID'         : 'your-clientID-here',
        'clientSecret'     : 'your-client-secret-here',
        'callbackURL'      : 'http://localhost:4000/auth/google/callback'
    }
};

Замените /passport.js следующим:

'use strict';

require('./mongoose')();
var passport = require('passport');
var TwitterTokenStrategy = require('passport-twitter-token');
var User = require('mongoose').model('User');
var FacebookTokenStrategy = require('passport-facebook-token');
var GoogleTokenStrategy = require('passport-google-token').Strategy;
var config = require('./config');

module.exports = function () {

    passport.use(new TwitterTokenStrategy({
            consumerKey: config.twitterAuth.consumerKey,
            consumerSecret: config.twitterAuth.consumerSecret,
            includeEmail: true
        },
        function (token, tokenSecret, profile, done) {
            User.upsertTwitterUser(token, tokenSecret, profile, function(err, user) {
                return done(err, user);
            });
        }));

    passport.use(new FacebookTokenStrategy({
            clientID: config.facebookAuth.clientID,
            clientSecret: config.facebookAuth.clientSecret
        },
        function (accessToken, refreshToken, profile, done) {
            User.upsertFbUser(accessToken, refreshToken, profile, function(err, user) {
                return done(err, user);
            });
        }));

    passport.use(new GoogleTokenStrategy({
            clientID: config.googleAuth.clientID,
            clientSecret: config.googleAuth.clientSecret
        },
        function (accessToken, refreshToken, profile, done) {
            User.upsertGoogleUser(accessToken, refreshToken, profile, function(err, user) {
                return done(err, user);
            });
        }));
};

Замените /routes/index.js следующим:

var express = require('express');
var router = express.Router();
var { generateToken, sendToken } = require('../utils/token.utils');
var passport = require('passport');
var config = require('../config');
var request = require('request');
require('../passport')();

router.route('/auth/twitter/reverse')
    .post(function(req, res) {
        request.post({
            url: 'https://api.twitter.com/oauth/request_token',
            oauth: {
                oauth_callback: "http%3A%2F%2Flocalhost%3A3000%2Ftwitter-callback",
                consumer_key: config.twitterAuth.consumerKey,
                consumer_secret: config.twitterAuth.consumerSecret
            }
        }, function (err, r, body) {
            if (err) {
                return res.send(500, { message: e.message });
            }
            var jsonStr = '{ "' + body.replace(/&/g, '", "').replace(/=/g, '": "') + '"}';
            res.send(JSON.parse(jsonStr));
        });
    });

router.route('/auth/twitter')
    .post((req, res, next) => {
        request.post({
            url: `https://api.twitter.com/oauth/access_token?oauth_verifier`,
            oauth: {
                consumer_key: config.twitterAuth.consumerKey,
                consumer_secret: config.twitterAuth.consumerSecret,
                token: req.query.oauth_token
            },
            form: { oauth_verifier: req.query.oauth_verifier }
        }, function (err, r, body) {
            if (err) {
                return res.send(500, { message: err.message });
            }

            const bodyString = '{ "' + body.replace(/&/g, '", "').replace(/=/g, '": "') + '"}';
            const parsedBody = JSON.parse(bodyString);

            req.body['oauth_token'] = parsedBody.oauth_token;
            req.body['oauth_token_secret'] = parsedBody.oauth_token_secret;
            req.body['user_id'] = parsedBody.user_id;

            next();
        });
    }, passport.authenticate('twitter-token', {session: false}), function(req, res, next) {
        if (!req.user) {
            return res.send(401, 'User Not Authenticated');
        }
        req.auth = {
            id: req.user.id
        };

        return next();
    }, generateToken, sendToken);

router.route('/auth/facebook')
    .post(passport.authenticate('facebook-token', {session: false}), function(req, res, next) {
        if (!req.user) {
            return res.send(401, 'User Not Authenticated');
        }
        req.auth = {
            id: req.user.id
        };

        next();
    }, generateToken, sendToken);

router.route('/auth/google')
    .post(passport.authenticate('google-token', {session: false}), function(req, res, next) {
        if (!req.user) {
            return res.send(401, 'User Not Authenticated');
        }
        req.auth = {
            id: req.user.id
        };

        next();
    }, generateToken, sendToken);

module.exports = router;

Сказав это и сделано, давайте снова заставим сервер работать. Получите демон Mongo, выполнив следующую команду в отдельном окне:

mongod

Теперь из корня серверного приложения запустите:

npm start

Если все сработало правильно, вернитесь в свое веб-приложение и нажмите эти кнопки. Теперь они все должны работать. Если вы похожи на меня, у вас неизбежно будет один и тот же адрес электронной почты для нескольких социальных платформ. Ваш сервер вернет ошибку 500 и будет жаловаться примерно на следующее:

MongoError: E11000 duplicate key error collection:

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

  1. Проверка того, зарегистрирован ли уже этот адрес электронной почты, и если это так, возвращается дружественная ошибка типа «Похоже, вы уже зарегистрированы в Facebook».
  2. Проверка того, зарегистрирован ли уже этот адрес электронной почты, и если это так, связывание двух учетных записей социальных сетей под одним и тем же пользователем.

В любом случае, это все, ребята!

Ознакомьтесь с исходным кодом этого руководства здесь: https://github.com/alien35/social-auth-example