В этом посте я объясню и предоставлю вам код, который позволяет пользователям входить в систему и подписываться на ваше приложение. Он построен в локальной среде и использует такие технологии, как MongoDB, Express.js, React.js и Node.js. Я ожидаю, что вы установите их все вместе с текстовым редактором. Я также рекомендую установить Mongo Compass, чтобы увидеть ваши данные.

Начнем с настройки. Мы собираемся использовать этот шаблонный код MERN. Это хорошая отправная точка, потому что она проста, в ней нет Redux, а серверная часть и интерфейсная часть находятся на одном порту (8080). Либо клонируйте, либо загрузите код под зеленой кнопкой справа.

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

npm install
npm install bcrypt --save

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

Вам нужно будет переименовать config.example.js в config.js, и он будет проигнорирован Git. Он содержит информацию о подключении к серверу. В этом посте мы будем работать только в среде разработки. Следовательно, нам нужно только изменить db_dev, чтобы он работал. Сделайте это mongodb://localhost:27017/login_demo_db.

Теперь вы должны быть готовы запустить шаблонный код MERN. После завершения установки и сохранения файла конфигурации запустите:

npm run start:dev

В вашем браузере на порту 8080 (http: // localhost: 8080) вы должны увидеть:

Модель пользователя

Мы будем создавать несколько новых файлов с участием наших пользователей. Нам нужно создать файл User (User.js) для представления нашей структуры пользовательских данных. Этот файл войдет в /server/models/ и будет содержать такие атрибуты пользователя, как:

  • Эл. адрес
  • Пароль
  • Дата регистрации
  • И все, что ваша платформа может захотеть сохранить ...

Содержимое нашего файла будет следующим:

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    default: ''
  },
  password: {
    type: String,
    default: ''
  },
  isDeleted: {
    type: Boolean,
    default: false
  },
  signUpDate: {
    type: Date,
    default: Date.now()
  }
});
UserSchema.methods.generateHash = function(password) {
  return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
UserSchema.methods.validPassword = function(password) {
  return bcrypt.compareSync(password, this.password);
};
module.exports = mongoose.model('User', UserSchema);

Зарегистрироваться

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

Начните с создания файла с именем signin.js в server/routes/api/. Этот файл будет содержать все конечные точки, связанные с входом в систему.

Вверху файла (ниже) вы увидите, что он начинается с импорта объекта модели User. Затем следует конечная точка API регистрации. В этой конечной точке шаги включают:

  1. Получить адрес электронной почты и пароль из тела запроса
  2. Убедитесь, что оба значения являются правильными
  3. Проверьте существующего пользователя с этим адресом электронной почты
  4. Создайте новый объект User и сохраните.
const User = require('../../models/User');

module.exports = (app) => {
  /*
   * Sign up
   */
  app.post('/api/account/signup', (req, res, next) => {
    const { body } = req;
    const {
      password
    } = body;
    let {
      email
    } = body;
    
    if (!email) {
      return res.send({
        success: false,
        message: 'Error: Email cannot be blank.'
      });
    }
    if (!password) {
      return res.send({
        success: false,
        message: 'Error: Password cannot be blank.'
      });
    }
    email = email.toLowerCase();
    email = email.trim();
    // Steps:
    // 1. Verify email doesn't exist
    // 2. Save
    User.find({
      email: email
    }, (err, previousUsers) => {
      if (err) {
        return res.send({
          success: false,
          message: 'Error: Server error'
        });
      } else if (previousUsers.length > 0) {
        return res.send({
          success: false,
          message: 'Error: Account already exist.'
        });
      }
      // Save the new user
      const newUser = new User();
      newUser.email = email;
      newUser.password = newUser.generateHash(password);
      newUser.save((err, user) => {
        if (err) {
          return res.send({
            success: false,
            message: 'Error: Server error'
          });
        }
        return res.send({
          success: true,
          message: 'Signed up'
        });
      });
    });
  }); // end of sign up endpoint
};

Наша регистрация завершена. Это не приводит к входу пользователя в систему, но создает учетную запись через API.

Затем пришло время добавить базовую форму React для обработки регистрации. Это не лучшая реализация с React, но я хочу, чтобы файловая структура была простой. Откройте client/app/components/Home/Home.js, и вы должны увидеть почти пустой компонент React.

В этом файле много чего происходит, поэтому вы также можете сослаться на полный файл здесь: https://gist.github.com/keithweaver/f7851be9029444c77bf7626add6a117a

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

Ваш импорт должен быть таким же, как показано ниже:

import React, { Component } from 'react';
import 'whatwg-fetch';

Верх файла должен выглядеть так:

class Home extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isLoading: true,
      token: '',
      signUpError: '',
      signInError: '',
      signInEmail: '',
      signInPassword: '',
      signUpEmail: '',
      signUpPassword: '',
    };
  }
  ... more below ...

Вам нужно будет создать несколько функций под конструктором для обновления значений состояния / текстового поля. Функция будет выглядеть так, как показано ниже, но вам нужно будет создать onTextboxChangeSignInEmail, onTextboxChangeSignInPassword, onTextboxChangeSignUpEmail & onTextboxChangeSignUpPassword. Вам нужно будет изменить значение, установленное в функции.

onTextboxChangeSignInEmail(event) {
  this.setState({
    signInEmail: event.target.value,
  });
}

Свяжите эти функции с компонентом в конструкторе так, чтобы под this.state = {} добавить

this.onTextboxChangeSignInEmail = this.onTextboxChangeSignInEmail.bind(this);
    this.onTextboxChangeSignInPassword = this.onTextboxChangeSignInPassword.bind(this);
    this.onTextboxChangeSignUpEmail = this.onTextboxChangeSignUpEmail.bind(this);
    this.onTextboxChangeSignUpPassword = this.onTextboxChangeSignUpPassword.bind(this);

Нам нужно будет создать функцию для отправки формы регистрации. Создайте еще одну функцию с именем onSignUp. Привяжите его в конструкторе.

this.onSignUp = this.onSignUp.bind(this);

Регистрация будет выглядеть так:

onSignUp() {
    // Grab state
    const {
      signUpEmail,
      signUpPassword,
    } = this.state;
    this.setState({
      isLoading: true,
    });
    // Post request to backend
    fetch('/api/account/signup', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        email: signUpEmail,
        password: signUpPassword,
      }),
    }).then(res => res.json())
      .then(json => {
        console.log('json', json);
        if (json.success) {
          this.setState({
            signUpError: json.message,
            isLoading: false,
            signUpEmail: '',
            signUpPassword: '',
          });
        } else {
          this.setState({
            signUpError: json.message,
            isLoading: false,
          });
        }
      });
  }

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

Последний шаг для регистрации - это функция render.

render() {
    const {
      isLoading,
      token,
      signInError,
      signInEmail,
      signInPassword,
      signUpEmail,
      signUpPassword,
      signUpError,
    } = this.state;
    if (isLoading) {
      return (<div><p>Loading...</p></div>);
    }
    if (!token) {
      return (
        <div>
          <div>
            {
              (signInError) ? (
                <p>{signInError}</p>
              ) : (null)
            }
            <p>Sign In</p>
            <input
              type="email"
              placeholder="Email"
              value={signInEmail}
              onChange={this.onTextboxChangeSignInEmail}
            />
            <br />
            <input
              type="password"
              placeholder="Password"
              value={signInPassword}
              onChange={this.onTextboxChangeSignInPassword}
            />
            <br />
            <button>Sign In</button>
          </div>
          <br />
          <br />
          <div>
            {
              (signUpError) ? (
                <p>{signUpError}</p>
              ) : (null)
            }
            <p>Sign Up</p>
            <input
              type="email"
              placeholder="Email"
              value={signUpEmail}
              onChange={this.onTextboxChangeSignUpEmail}
            /><br />
            <input
              type="password"
              placeholder="Password"
              value={signUpPassword}
              onChange={this.onTextboxChangeSignUpPassword}
            /><br />
            <button onClick={this.onSignUp}>Sign Up</button>
          </div>
       </div>
      );
    }
    return (
      <div>
        <p>Signed in</p>
      </div>
    );
  }

Это первый раз, когда вы видите токен. На данный момент он будет нулевым. Нам нужно временно добавить componentDidMount, чтобы установить для загрузки значение false. Ниже конструктора добавьте:

componentDidMount() {
   this.setState({
     isLoading: false
   });
}

Если вы запустите сервер с:

npm run start:dev

Перейдите по адресу http: // localhost: 8080. Вы должны увидеть форму регистрации. При условии, что вы все настроили правильно. Вы должны иметь возможность зарегистрировать своего нового пользователя. Используя такой инструмент, как Mongo Compass, вы сможете увидеть своего нового пользователя в своей базе данных с зашифрованным паролем.

Авторизоваться

Мы собираемся настроить вход, выйти и проверить пользовательский API. Начните с создания объекта модели UserSession. В server/models/UserSession.js и добавить:

const mongoose = require('mongoose');
const UserSessionSchema = new mongoose.Schema({
  userId: {
    type: String,
    default: ''
  },
  timestamp: {
    type: Date,
    default: Date.now()
  },
  isDeleted: {
    type: Boolean,
    default: false
  }
});
module.exports = mongoose.model('UserSession', UserSessionSchema);

Этот файл довольно простой. userId - это идентификатор документа Mongo (поэтому mongo создает _id во всех документах) для пользователя. Мы собираемся использовать это как первичный ключ. Вы можете изменить это на электронную почту пользователя или что-то еще. Просто знайте, что это не должно измениться. Токен / код сеанса будет идентификатором документа UserSession, но я объясню это ниже.

Откройте тот же серверный файл: server/routes/api/signin.js и добавьте еще одну конечную точку.

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

Сначала импортируйте объект модели сеанса пользователя.

const UserSession = require('../../models/UserSession');

Затем добавьте конечную точку (это входит в module.export)

app.post('/api/account/signin', (req, res, next) => {
    const { body } = req;
    const {
      password
    } = body;
    let {
      email
    } = body;
    if (!email) {
      return res.send({
        success: false,
        message: 'Error: Email cannot be blank.'
      });
    }
    if (!password) {
      return res.send({
        success: false,
        message: 'Error: Password cannot be blank.'
      });
    }
    email = email.toLowerCase();
    email = email.trim();
    User.find({
      email: email
    }, (err, users) => {
      if (err) {
        console.log('err 2:', err);
        return res.send({
          success: false,
          message: 'Error: server error'
        });
      }
      if (users.length != 1) {
        return res.send({
          success: false,
          message: 'Error: Invalid'
        });
      }
      const user = users[0];
      if (!user.validPassword(password)) {
        return res.send({
          success: false,
          message: 'Error: Invalid'
        });
      }
      // Otherwise correct user
      const userSession = new UserSession();
      userSession.userId = user._id;
      userSession.save((err, doc) => {
        if (err) {
          console.log(err);
          return res.send({
            success: false,
            message: 'Error: server error'
          });
        }
        return res.send({
          success: true,
          message: 'Valid sign in',
          token: doc._id
        });
      });
    });
  });

Для выхода из системы нам просто нужно изменить сеанс пользователя на удаленный. API принимает токен сеанса и обновляет документ как удаленный. Снова в экспорте модуля мы добавим GET запрос.

app.get('/api/account/logout', (req, res, next) => {
    // Get the token
    const { query } = req;
    const { token } = query;
    // ?token=test
    // Verify the token is one of a kind and it's not deleted.
    UserSession.findOneAndUpdate({
      _id: token,
      isDeleted: false
    }, {
      $set: {
        isDeleted:true
      }
    }, null, (err, sessions) => {
      if (err) {
        console.log(err);
        return res.send({
          success: false,
          message: 'Error: Server error'
        });
      }
      return res.send({
        success: true,
        message: 'Good'
      });
    });
  });

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

Проверка (которая будет выполняться поверх конечной точки действия) выглядит следующим образом:

app.get('/api/account/verify', (req, res, next) => {
    // Get the token
    const { query } = req;
    const { token } = query;
    // ?token=test
    // Verify the token is one of a kind and it's not deleted.
    UserSession.find({
      _id: token,
      isDeleted: false
    }, (err, sessions) => {
      if (err) {
        console.log(err);
        return res.send({
          success: false,
          message: 'Error: Server error'
        });
      }
      if (sessions.length != 1) {
        return res.send({
          success: false,
          message: 'Error: Invalid'
        });
      } else {
        // DO ACTION
        return res.send({
          success: true,
          message: 'Good'
        });
      }
    });
  });

Смущенный? Вот полностью заполненный файл: https://gist.github.com/keithweaver/e63e0915b2b7ea011443ff91b0aa9726

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

<button onClick={this.onSignIn}>Sign In</button>

Затем перейдите к конструктору и добавьте

this.onSignIn = this.onSignIn.bind(this);

Последний бит - создать фактическую функцию для onSignIn, поэтому ниже onSignUp добавьте

onSignIn() {
    // Grab state
    const {
      signInEmail,
      signInPassword,
    } = this.state;
    this.setState({
      isLoading: true,
    });
    // Post request to backend
    fetch('/api/account/signin', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        email: signInEmail,
        password: signInPassword,
      }),
    }).then(res => res.json())
      .then(json => {
        console.log('json', json);
        if (json.success) {
          setInStorage('the_main_app', { token: json.token });
          this.setState({
            signInError: json.message,
            isLoading: false,
            signInPassword: '',
            signInEmail: '',
            token: json.token,
          });
        } else {
          this.setState({
            signInError: json.message,
            isLoading: false,
          });
        }
      });
  }

Прежде чем мы протестируем это, я собираюсь объяснить, как мы узнаем, что кто-то вошел в систему. Мы собираемся использовать localStorage и хранить там наш токен. Если вы посмотрите на обратный вызов метода signIn, вы увидите setInStorage. Когда пользователь открывает нашу страницу, мы проверяем хранилище на предмет наличия токена. Если он у них есть, мы предполагаем, что они вошли в систему, или можем проверить, что это действительный токен. Если у них нет токена, мы загружаем нашу форму входа и регистрации. Когда они входят в систему с действительными учетными данными, мы сохраняем новый токен.

Добавьте к импорту Home.js.

import {
  setInStorage,
  getFromStorage,
} from '../../utils/storage';

Нам нужно создать служебные функции. Итак, создайте новую папку и файл в client/app/utils/storage.js и добавьте эти две функции в:

export function getFromStorage(key) {
  if (!key) {
    return null;
  }
  try {
    const valueStr = localStorage.getItem(key);
    if (valueStr) {
      return JSON.parse(valueStr);
    }
    return null;
  } catch (err) {
    return null;
  }
}
export function setInStorage(key, obj) {
  if (!key) {
    console.error('Error: Key is missing');
  }
  try {
    localStorage.setItem(key, JSON.stringify(obj));
  } catch (err) {
    console.error(err);
  }
}

Мы собираемся поместить это получение / проверку токена в componentDidMount жизненного цикла React.

componentDidMount() {
    const obj = getFromStorage('the_main_app');
    if (obj && obj.token) {
      const { token } = obj;
      // Verify token
      fetch('/api/account/verify?token=' + token)
        .then(res => res.json())
        .then(json => {
          if (json.success) {
            this.setState({
              token,
              isLoading: false
            });
          } else {
            this.setState({
              isLoading: false,
            });
          }
        });
    } else {
      this.setState({
        isLoading: false,
      });
    }
  }

Измените the_main_app на имя вашего веб-сайта. Он просто должен быть уникальным. Убедитесь, что вы изменили все его экземпляры.

Опять же, перед тестированием, мы должны добавить выход из системы. Сначала обновите HTML-код «Выполнен вход» визуализации. Брать

<div>
  <p>Signed in</p>
</div>

И сделай это

<div>
  <p>Account</p>
  <button onClick={this.logout}>Logout</button>
</div>

Свяжите свою функцию выхода из системы в конструкторе.

this.logout = this.logout.bind(this);

Создайте logout под функцией signIn. Он очищает токен из локального хранилища и сообщает серверной части «удалить» сеанс пользователя.

logout() {
    this.setState({
      isLoading: true,
    });
    const obj = getFromStorage('the_main_app');
    if (obj && obj.token) {
      const { token } = obj;
      // Verify token
      fetch('/api/account/logout?token=' + token)
        .then(res => res.json())
        .then(json => {
          if (json.success) {
            this.setState({
              token: '',
              isLoading: false
            });
          } else {
            this.setState({
              isLoading: false,
            });
          }
        });
    } else {
      this.setState({
        isLoading: false,
      });
    }
  }

Теперь, наконец, пришло время для тестирования. Перезагрузите ваш сервер.

npm run start:dev

Откройте http: // localhost: 8080 /, и вы должны просто увидеть форму входа и регистрации. Поскольку в предыдущем тесте мы уже регистрировались. Мы можем просто использовать эти учетные данные для входа в систему. Если вы еще не зарегистрировались, проверьте это. Убедитесь, что ваш пользователь находится в вашей базе данных.

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

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

У вас есть система входа в систему, которая работает в частной среде.

Следующие шаги

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

Поставьте, если вам понравилась эта статья. Я стараюсь публиковать похожие статьи как можно чаще и каждую неделю выпускаю Youtube-видео.