Стратегия JWT Token для интерфейса и бэкэнд

Я пишу приложение с интерфейсом в emberjs и backend/server-side на сервере nodejs. У меня emberjs настроен так, что пользователь может войти в систему/зарегистрироваться с сторонним Oauth (google, twitter, Facebook). У меня есть бэкэнд, написанный на сервере express nodejs, на котором размещены API RESTful.

У меня нет DB, связанного с emberjs, и я не думаю, что я должен так или иначе, потому что это строго клиентский код. Я планирую использовать JWT для связи между клиентской и серверной сторонами. Когда пользователь входит в систему со своим oauth-кредитом, я получаю объект JSON от поставщика с помощью uid, name, login, access_token и других деталей.

Я борюсь с выбором стратегии управления регистрацией пользователей. Нет процесса регистрации, поскольку он OAuth. Итак, поток - это если пользователь не находится в моем db, создайте его. Я не поддерживаю аутентификацию по электронной почте/паролю. Каким будет поток, когда пользователь впервые заходит с поставщиком OAuth? Должен ли emberjs отправлять все данные в бэкэнд на каждом знаке, чтобы бэкэнд мог добавлять новых пользователей в db?

Что должно быть частью моего тела JWT? Я думал, что uid и поставщик предоставили токен доступа. Одна из проблем, о которых я могу думать, заключается в том, что токен доступа конкретного поставщика может измениться. Пользователь может отменить маркер с сайта поставщика и снова зарегистрировать его с помощью emberjs.

Я открыт для написания front-end в любой другой стороне клиентской части JavaScript, если это упростит.

Ответы

Ответ 1

Если мы говорим не только о работе, но и о проверке без аутентификации, вам нужно будет рассмотреть правильную стратегию с помощью токенов access и refresh.

  • Ток доступа - это токен, который обеспечивает доступ к защищенному ресурсу. Expiration здесь может быть установлен примерно через ~ 1 час (зависит от ваших соображений).

  • Обновить токен - это специальный токен, который должен использоваться для создания дополнительных access token в случае, если он был истек или пользовательский сеанс обновлен. Очевидно, вам нужно сделать это долговечным (по сравнению с access token) и обеспечить как можно больше безопасности. Expiration здесь может быть установлен примерно через ~ 10 дней или даже больше (также зависит от ваших соображений).

FYI: Поскольку refresh tokens долговечны, чтобы сделать их действительно надежными, вы можете захотеть их сохранить в своей базе данных (обновлять запросы токенов выполняются редко). Таким образом, допустим, даже если ваш токен обновления был взломан каким-то образом, а кто-то восстановил токены access/refresh, конечно, вы потеряете права доступа, но тогда вы все равно можете войти в систему, так как вы знаете логин/пароль (в случае, если вы будет использовать их позже) или просто путем входа в систему через любую социальную сеть.


Где хранить эти токены?

В основном есть 2 общих места:

  • Веб-хранилище HTML5 (localStorage/sessionStorage)

Хорошо идти, но в то же время достаточно рискованно. Хранение доступно через код javascript в том же домене. Это означает, что если у вас есть XSS, ваши токены могут быть взломаны. Поэтому, выбирая этот метод, вы должны позаботиться и закодировать/удалить все ненадежные данные. И даже если вы это сделали, я уверен, что вы используете несколько сторонних клиентских модулей, и нет никакой гарантии, что у кого-то есть какой-то вредоносный код.

Также Web Storage не применяет никаких безопасных стандартов во время передачи. Поэтому вам нужно быть уверенным, что JWT отправляется через HTTPS и никогда HTTP.

  1. Cookies

С конкретными HttpOnly куки файлы cookie недоступны через javascript и невосприимчивы к XSS. Вы также можете установить флаг Secure cookie, чтобы гарантировать, что cookie отправляется только через HTTPS. Тем не менее, файлы cookie уязвимы для атаки другого типа: подделка запроса на межсайтовый запрос (CSRF). В этом случае CSRF можно было бы предотвратить, используя какие-то синхронизированные шаблоны токенов. Существует хорошая реализация в AngularJS, в разделе Вопросы безопасности.

статья, которую вы можете захотеть следовать.

Чтобы проиллюстрировать, как это работает в целом:

http://jlabusch.github.io/oauth2-server/img/diag_refresh_token.png


Несколько слов о JWT:

Чтобы было ясно, есть действительно классный JWT Debugger от парней Auth0. Существует 2 (иногда 3) типа общих претензий: public, privateзарезервировано).

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

{     
  name: "Dave Doe",
  isAdmin: true,
  providerToken: '...' // should be verified then separately
}

Подробнее о структуре JWT вы найдете здесь.

Ответ 2

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

Тем не менее, если вы хотите сделать это вручную или хотите получить базовое понимание, вот поток, который я бы использовал:

  • У Frontend есть список входа в систему Вход в систему с Google/Facebook и т.д., где OAuth реализован.

  • Успешный OAuth приводит к uid, login, access_token и т.д. (объект JSON)

  • Вы отправляете объект JSON на ваш маршрут /login/ в вашем приложении Node.js. (Да, вы отправляете весь ответ, независимо от того, является ли он новым или существующим пользователем. Отправка дополнительных данных здесь лучше, чем выполнение двух запросов)

  • Бэкэнд-приложение читает uid и access_token. Убедитесь, что access_token действителен, следуя (https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken) или запрашивая данные пользователя у поставщика, используя токен доступа. (Это приведет к сбою за недействительный токен доступа, поскольку токены доступа OAuth создаются на основе каждого приложения/разработчика). Теперь выполните поиск в своей БД.

  • Если в базе данных существует uid, вы обновляете пользователя access_token и expiresIn в БД. (Access_token позволяет вам получать дополнительную информацию от Facebook для этого конкретного пользователя и обычно обеспечивает доступ в течение нескольких часов.)

  • Кроме того, вы создаете нового пользователя с uid, login и т.д.

  • После обновления access_token или создания нового пользователя вы отправляете токен JWT, содержащий uid. (Кодируйте jwt с секретом, это гарантирует, что оно было отправлено вами и не было подделано. Checkout https://github.com/auth0/express-jwt)

  • В интерфейсе после того, как пользователь получил jwt от /login, сохраните его до sessionStorage на sessionStorage.setItem('jwt', token);

  • В интерфейсе также добавьте следующее:

if ($window.sessionStorage.token) { xhr.setRequestHeader("Authorization", $window.sessionStorage.token); }

Это гарантирует, что если есть токен jwt, он отправляется с каждым запросом.

  1. В файле Node.js app.js добавьте

app.use(jwt({ secret: 'shhhhhhared-secret'}).unless({path: ['/login']}));

Это подтвердит, что jwt для чего-либо в вашем пути, гарантируя, что пользователь вошел в систему, в противном случае не разрешает доступ и перенаправление на страницу входа. Случай с исключениями здесь /login, так как там, где вы даете своим новым или неаутентифицированным пользователям JWT.

Дополнительную информацию о URL-адресе Github можно найти в том, как получить токен и узнать, какой пользовательский запрос вы сейчас используете.

Ответ 3

Чтобы ответить на два заданных вами вопроса:

Каким будет поток при входе пользователя с поставщиком OAuth для первый раз? Если emberjs отправит все детали на каждый знак, чтобы бэкэнд мог добавлять новых пользователей в db?

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

Что должно быть частью моего тела JWT? Я думал uid и провайдера доступный токен доступа. Один из вопросов, о котором я могу думать, - это тот провайдер конкретный токен доступа может измениться. Пользователь может отменить токен из сайт провайдера и подписывается снова с emberjs.

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

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

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

{
    iss: "https://YOUR_NAMESPACE",
    sub: "{connection}|{user_id}",
    aud: "YOUR_CLIENT_ID",
    exp: 1372674336,
    iat: 1372638336
}