Skip to content

Instantly share code, notes, and snippets.

@ripdamage
Forked from zmts/tokens.md
Created October 22, 2023 15:11
Show Gist options
  • Save ripdamage/ff3fc841d430926422f2d9db08210e51 to your computer and use it in GitHub Desktop.
Save ripdamage/ff3fc841d430926422f2d9db08210e51 to your computer and use it in GitHub Desktop.

Revisions

  1. @zmts zmts revised this gist Jun 27, 2023. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -164,7 +164,8 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    1. Front-end делает кол `POST: api/auth/logout` c __refreshToken__ в куке или бади (лучше в куки)
    2. Front-end удаляет локально сохраненный в памяти __accessToken__
    3. Back-end удаляет запись из таблицы `refreshSessions` по __refreshToken__
    4. __accessToken__ умирает по истечению его строка жизни. Руками банить, удалять, хранить __accessToken__ не нужно, это нарушает всю суть эксесс токена.

    __accessToken__ умирает по истечению строка его жизни. Руками банить, удалять, хранить __accessToken__ не нужно, это нарушает всю суть эксесс токена.

    ## В случае кражи access токена и refresh куки:
    1. Хакер воспользовался __access token'ом__
  2. @zmts zmts revised this gist Jun 27, 2023. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -160,6 +160,11 @@ _Tip:_ Для отправки запроса с куками для `axios` е

    Вопрос зачем __refresh token'y__ срок жизни, если он обновляется каждый раз при обновлении __access token'a__ ? Это сделано на случай, если юзер будет в офлайне более 60 дней, тогда придется заново вбить логин/пароль.

    ## Logout (api/auth/logout)
    1. Front-end делает кол `POST: api/auth/logout` c __refreshToken__ в куке или бади (лучше в куки)
    2. Front-end удаляет локально сохраненный в памяти __accessToken__
    3. Back-end удаляет запись из таблицы `refreshSessions` по __refreshToken__
    4. __accessToken__ умирает по истечению его строка жизни. Руками банить, удалять, хранить __accessToken__ не нужно, это нарушает всю суть эксесс токена.

    ## В случае кражи access токена и refresh куки:
    1. Хакер воспользовался __access token'ом__
  3. @zmts zmts revised this gist Nov 30, 2021. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -173,8 +173,8 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    9. Сервер перенаправляет юзера на форму аутентификации
    10. Юзер вводит логин/пароль

    ## В случае кражи access токена, refresh куки и fingerprint'а (без примера в кодовой базе `supra-api-nodejs`):
    Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний и наиболее неудобный с точки зрения UX.
    ## В случае кражи access токена, refresh куки и fingerprint'а:
    Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний и наиболее неудобный с точки зрения UX (без примера в кодовой базе `supra-api-nodejs`).

    Предложу несколько вариантов решения данной проблемы:

  4. @zmts zmts revised this gist Mar 21, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -193,7 +193,7 @@ _UX минус: нужно логинится с каждого нового IP.
    4. На сервере создается новый __refresh__ токен ("от хакера")
    5. Хакер получает новую пару токенов
    6. Юзер пробует отправить запрос на сервер >> обнаруживается что __refresh__ токен не валиден
    7. Сервер сервер удаляет все сессии юзера, в последствии чего хакер больше не сможет обновлять __access__ токен
    7. Сервер удаляет все сессии юзера, в последствии чего хакер больше не сможет обновлять __access__ токен
    8. Сервер создает новую сессию для пользователя

    _UX минус: в каждом случае когда сервер не будет находить рефреш токен - будут сбрасиватся все сессии юзера на всех устройствах._
  5. @zmts zmts revised this gist Aug 27, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -38,7 +38,7 @@ __Токены__ предоставляют собой средство __авт

    При попытке хакером подменить данные в __header'ре__ или __payload'е__, токен станет не валидным, поскольку сигнатура не будет соответствовать изначальным значениям. А возможность сгенерировать новую сигнатуру у хакера отсутствует, поскольку секретный ключ для зашифровки лежит на сервере.

    __access token__ - используется для __авторизации запросов__ и хранения дополнительной информации о пользователе (аля __user_id__, __user_role__ или еще что либо, эту информацию также называет __payload__). __Сам токен храним не в localStorage как это обычно делают, а в памяти клиентского приложения.__
    __access token__ - используется для __авторизации запросов__ и хранения дополнительной информации о пользователе (аля __user_id__, __user_role__ или еще что либо, эту информацию также называет __payload__). Все поля в __payload__ это свободный набор полей необходимый для реализации вашей частной бизнес логики. То бишь __user_id__ и __user_role__ не являются требованием и представляют собой исключительно частный случай. __Сам токен храним не в localStorage как это обычно делают, а в памяти клиентского приложения.__

    __refresh token__ - выдается сервером по результам успешной аутентификации и используется для получения новой пары __access/refresh__ токенов. __Храним исключительно в httpOnly куке__.

  6. @zmts zmts revised this gist Aug 25, 2020. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    # Про токены, JSON Web Tokens (JWT), аутентификацию и авторизацию. Token-Based Authentication

    `Last major update: 20.04.2020`
    `Last major update: 25.08.2020`

    - Что такое авторизация/аутентификация
    - Где хранить токены
    @@ -174,7 +174,7 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    10. Юзер вводит логин/пароль

    ## В случае кражи access токена, refresh куки и fingerprint'а (без примера в кодовой базе `supra-api-nodejs`):
    Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний и наиболее неудобрый с точки зрения UX.
    Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний и наиболее неудобный с точки зрения UX.

    Предложу несколько вариантов решения данной проблемы:

    @@ -184,7 +184,7 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    3. __Хакер__ отправляет __refresh__ куку и __fingerprint__
    4. Сервер проверяет IP __хакера__, __хакер__ идет лесом

    UX минус: нужно логинится с каждого нового IP.
    _UX минус: нужно логинится с каждого нового IP._

    - Удалять все сессии в случае если __refresh__ токен не найден
    1. Хакер воспользовался __access token'ом__
    @@ -196,7 +196,7 @@ UX минус: нужно логинится с каждого нового IP.
    7. Сервер сервер удаляет все сессии юзера, в последствии чего хакер больше не сможет обновлять __access__ токен
    8. Сервер создает новую сессию для пользователя

    UX минус: в каждом случае когда сервер не будет находить рефреш токен - будут сбрасиватся все сессии юзера на всех устройствах.
    _UX минус: в каждом случае когда сервер не будет находить рефреш токен - будут сбрасиватся все сессии юзера на всех устройствах._

    ## Зачем все это ? JWT vs Cookie sessions
    Зачем этот весь геморой ? Почему не юзать старые добрые cookie sessions ? Чем не угодили куки ?
  7. @zmts zmts revised this gist Aug 25, 2020. 1 changed file with 17 additions and 5 deletions.
    22 changes: 17 additions & 5 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -173,18 +173,30 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    9. Сервер перенаправляет юзера на форму аутентификации
    10. Юзер вводит логин/пароль

    ## В случае кражи access токена, refresh куки и fingerprint'а:
    Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний.
    ## В случае кражи access токена, refresh куки и fingerprint'а (без примера в кодовой базе `supra-api-nodejs`):
    Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний и наиболее неудобрый с точки зрения UX.

    Предложу несколько вариантов решения данной проблемы:

    - Хранить IP или Subnet залогиненного клиента
    1. Хакер воспользовался __access token'ом__
    2. Закончилось время жизни __access token'на__
    3. __Хакер__ отправляет __refresh__ куку и __fingerprint__
    4. Сервер проверяет IP __хакера__, __хакер__ идет лесом

    UX минус: нужно логинится с каждого нового IP.

    - Удалять все сессии в случае если __refresh__ токен не найден
    1. Хакер воспользовался __access token'ом__
    2. Закончилось время жизни __access token'на__
    3. __Хакер__ отправляет __refresh__ куку и __fingerprint__
    4. На сервере создается новый __refresh__ токен ("от хакера")
    5. Хакер получает новую пару токенов
    6. Юзер пробует отправить запрос на сервер >> обнаруживается что __refresh__ токен не валиден
    7. Сервер перенаправляет юзера на форму аутентификации
    8. Юзер вводит логин/пароль
    9. Создается новый __refresh__ токен >> __refresh__ токен "от хакера" становится не валиден
    7. Сервер сервер удаляет все сессии юзера, в последствии чего хакер больше не сможет обновлять __access__ токен
    8. Сервер создает новую сессию для пользователя

    UX минус: в каждом случае когда сервер не будет находить рефреш токен - будут сбрасиватся все сессии юзера на всех устройствах.

    ## Зачем все это ? JWT vs Cookie sessions
    Зачем этот весь геморой ? Почему не юзать старые добрые cookie sessions ? Чем не угодили куки ?
  8. @zmts zmts revised this gist Jun 17, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -156,7 +156,7 @@ Set-Cookie: refreshToken='c84f18a2-c6c7-4850-be15-93f9cbaef3b3'; HttpOnly // д
    _Tip:_ Для отправки запроса с куками для `axios` есть опция `{ withCredentials: true }`

    ## Ключевой момент:
    В момент рефреша то есть обновления __access token'a__ обновляются __ОБА__ токена. Но как же __refresh token__ может сам себя обновить, он ведь создается только после успешной аунтефикации ? __refresh token__ в момент рефреша сравнивает себя с тем __refresh token'ом__ который лежит в БД и вслучае успеха, а также если у него не истек срок, система рефрешит токены.
    В момент рефреша то есть обновления __access token'a__ обновляются __ОБА__ токена. Но как же __refresh token__ может сам себя обновить, он ведь создается только после успешной аутентификации ? __refresh token__ в момент рефреша сравнивает себя с тем __refresh token'ом__ который лежит в БД и вслучае успеха, а также если у него не истек срок, система рефрешит токены.

    Вопрос зачем __refresh token'y__ срок жизни, если он обновляется каждый раз при обновлении __access token'a__ ? Это сделано на случай, если юзер будет в офлайне более 60 дней, тогда придется заново вбить логин/пароль.

  9. @zmts zmts revised this gist May 6, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -212,7 +212,7 @@ __Front-end:__
    - https://github.com/zmts/beauty-vuejs-boilerplate/blob/master/src/services/auth.service.js

    __Back-end:__
    - https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth
    - https://github.com/zmts/supra-api-nodejs/tree/master/modules/auth

    ### Info:
    - https://www.youtube.com/playlist?list=PLvTBThJr861y60LQrUGpJNPu3Nt2EeQsP
  10. @zmts zmts revised this gist May 5, 2020. 1 changed file with 20 additions and 4 deletions.
    24 changes: 20 additions & 4 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -89,8 +89,16 @@ server {
    3. В случае удачи создает и записывает сессию в БД `{ userId: uuid, refreshToken: uuid, expiresIn: int, fingerprint: string, ... }` (схема таблицы ниже)
    4. Создает __access token__
    5. Отправляет клиенту __access и refresh token uuid__ (взятый из выше созданной сессии)
    6. __access__ возвращает в теле запроса, __refresh__ устанавливает в качестве __httpOnly__ куки
    7. Клиент сохраняет токены(__access__ в памяти приложения, __refresh__ сетится как кука автоматом)
    ```
    Set-Cookie: refreshToken='c84f18a2-c6c7-4850-be15-93f9cbaef3b3'; HttpOnly // для браузера
    {
    body: {
    accessToken: 'eyJhbGciOiJIUzUxMiIsI...',
    refreshToken: 'c84f18a2-c6c7-4850-be15-93f9cbaef3b3' // для мобильных приложений
    }
    }
    ```
    6. Клиент сохраняет токены(__access__ в памяти приложения, __refresh__ сетится как кука автоматом)

    На что нужно обратить внимание при установке __refresh__ куки:
    - `maxAge` куки ставим равную `expiresIn` из выше созданной сессии
    @@ -135,7 +143,15 @@ CREATE TABLE refreshSessions (
    7. В случае успеха создает новую рефреш-сессию и записывает ее в БД
    8. Создает __access token__
    8. Отправляет клиенту __access и refresh token uuid__ (взятый из выше созданной рефреш-сессии)
    9. __access__ возвращает в теле запроса, __refresh__ устанавливает в качестве __httpOnly__ куки
    ```
    Set-Cookie: refreshToken='c84f18a2-c6c7-4850-be15-93f9cbaef3b3'; HttpOnly // для браузера
    {
    body: {
    accessToken: 'eyJhbGciOiJIUzUxMiIsI...',
    refreshToken: 'c84f18a2-c6c7-4850-be15-93f9cbaef3b3' // для мобильных приложений
    }
    }
    ```

    _Tip:_ Для отправки запроса с куками для `axios` есть опция `{ withCredentials: true }`

    @@ -230,4 +246,4 @@ __Back-end:__
    - https://t.me/why_jwt_is_bad

    ---
    _Комментарии периодически подчищаются_
    _Комментарии периодически подчищаются_
  11. @zmts zmts revised this gist May 4, 2020. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -107,6 +107,8 @@ __Стоит заметить, что процесс добавления сес
    - Более подробно: https://player.vimeo.com/video/151208427
    - Пример ф-ции получения такого хеша: https://gist.github.com/zmts/b26ba9a61aa0b93126fc6979e7338ca3

    В случае если клиент не браузер, а мобильное приложение, в качестве __fingerprint__ используем любую уникальную строку(тот же `uuid`) персистентно хранящуюся на устройстве.

    ## Рефреш токенов (api/auth/refresh-tokens):
    Для использования возможности аутентификации на более чем одном девайсе необходимо хранить все рефреш токены по каждому юзеру. Я храню это список в PostgreSQL таблице(а надо бы в Redis'е). В процессе каждого логина создается запись с IP/Fingerprint и другой мета информацией, так званая __рефреш-сессия__.
    ```
  12. @zmts zmts revised this gist Apr 23, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -96,7 +96,7 @@ server {
    - `maxAge` куки ставим равную `expiresIn` из выше созданной сессии
    - В `path` ставим корневой роут `auth` контроллера (`/api/auth`) это важно, таким образом токен получат только те хендлеры которым он нужен(`/api/auth/logout` и `/api/auth/rerfesh-tokens`), остальные обойдутся(нечего зря почём отправлять __sensitive data__).

    __Стоит заметить, что процесс добавления сессии в таблицу должен имеет свои меры безопасности.__ При добавлении стоит проверять сколько сессий всего есть у юзера и, если их слишком много или юзер конектится одновременно из нескольких подсетей, стоит предпринять меры. Имплементируя данную проверку, я проверяю только что бы юзер имел максимум до 5 одновременных сессий максимум, и на 6'ой удаляю все остальные сессии кроме текущей(6'ой). Все остальные проверки на ваше усмотрение в зависимости от задачи.
    __Стоит заметить, что процесс добавления сессии в таблицу должен имеет свои меры безопасности.__ При добавлении стоит проверять сколько рефреш-сессий всего есть у юзера и, если их слишком много или юзер конектится одновременно из нескольких подсетей, стоит предпринять меры. Имплементируя данную проверку, я проверяю только что бы юзер имел максимум до 5 одновременных рефреш-сессий максимум, и при попытке установить следующую удаляю предыдущие. Все остальные проверки на ваше усмотрение в зависимости от задачи.

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

  13. @zmts zmts revised this gist Apr 23, 2020. 1 changed file with 7 additions and 4 deletions.
    11 changes: 7 additions & 4 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -49,10 +49,13 @@ __Поскольку токены(а данном случае access) это н
    __Роль рефреш токенов и зачем их хранить в БД.__ Рефреш на сервере хранится для учета доступа и инвалидации краденых токенов. Таким образом сервер наверняка знает о клиентах которым стоит доверять(кому позволено авторизоваться). Если не хранить рефреш токен в БД то велика вероятность того что токены будут бесконтрольно гулять по рукам злоумышленников. Для отслеживания которых нам придется заводить черный список и периодически чистить его от просроченных. В место этого мы храним лимитированный список белых токенов для каждого юзера отдельно и в случае кражи у нас уже есть механизм противодействия(описано ниже).

    ## Как ставить куки ?
    Для того что бы `refreshToken` кука была успешно уставленна и отправлена браузером, адреса эндпоинтов аутентификации(`/api/auth/login`, `/api/auth/refresh-tokens`, `/api/auth/logout`) должны располагася в доменном пространстве сайта. Вот так:
    - Адрес сайта в браузере: `super.com`
    - Домен в опциях куки: `domain: '.super.com'`
    - Path в опциях куки: `path: '/api/auth'`
    Для того что бы `refreshToken` кука была успешно уставленна и отправлена браузером, адреса эндпоинтов аутентификации(`/api/auth/login`, `/api/auth/refresh-tokens`, `/api/auth/logout`) должны располагася в доменном пространстве сайта. Тоесть для домена `super.com` на сервере ставим куку с такими опциями:
    ```
    {
    domain: '.super.com',
    path: '/api/auth'
    }
    ```

    Таким образом кука установится в браузер и прийдет на все эндпоинты по адресу `super.com/api/auth/<any-path>`

  14. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 10 additions and 10 deletions.
    20 changes: 10 additions & 10 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -105,9 +105,9 @@ __Стоит заметить, что процесс добавления сес
    - Пример ф-ции получения такого хеша: https://gist.github.com/zmts/b26ba9a61aa0b93126fc6979e7338ca3

    ## Рефреш токенов (api/auth/refresh-tokens):
    Для использования возможности аутентификации на более чем одном девайсе необходимо хранить все рефреш токены по каждому юзеру. Я храню это список в PostgreSQL таблице(а надо бы в Redis'е). В процессе каждого логина создается запись с IP/Fingerprint и другой мета информацией, то есть сессия.
    Для использования возможности аутентификации на более чем одном девайсе необходимо хранить все рефреш токены по каждому юзеру. Я храню это список в PostgreSQL таблице(а надо бы в Redis'е). В процессе каждого логина создается запись с IP/Fingerprint и другой мета информацией, так званая __рефреш-сессия__.
    ```
    CREATE TABLE sessions (
    CREATE TABLE refreshSessions (
    "id" SERIAL PRIMARY KEY,
    "userId" uuid REFERENCES users(id) ON DELETE CASCADE,
    "refreshToken" uuid NOT NULL,
    @@ -121,15 +121,15 @@ CREATE TABLE sessions (

    1. Клиент(фронтенд) проверяет перед запросом не истекло ли время жизни __access token'на__
    2. Если истекло клиент делает запрос на `POST auth/refresh-tokens` `{ fingerprint: string }` в `body` и соответственно `refreshToken` куку.
    3. Сервер получает запись сессии по UUID'у рефреш токена
    4. Сохраняет текущую сессию в переменную и удаляет ее из таблицы
    5. Проверяет текущую сессию:
    3. Сервер получает запись рефреш-сессии по UUID'у рефреш токена
    4. Сохраняет текущую рефреш-сессию в переменную и удаляет ее из таблицы
    5. Проверяет текущую рефреш-сессию:
    1. Не истекло ли время жизни
    2. На соответствие старого __fingerprint'a__ полученного из текущей сессии с новым полученным из тела запроса
    6. В случае негативного результата бросает ошибку `TOKEN_EXPIRED`/`INVALID_SESSION`
    7. В случае успеха создает новую сессию и записывает ее в БД
    2. На соответствие старого __fingerprint'a__ полученного из текущей рефреш-сессии с новым полученным из тела запроса
    6. В случае негативного результата бросает ошибку `TOKEN_EXPIRED`/`INVALID_REFRESH_SESSION`
    7. В случае успеха создает новую рефреш-сессию и записывает ее в БД
    8. Создает __access token__
    8. Отправляет клиенту __access и refresh token uuid__ (взятый из выше созданной сессии)
    8. Отправляет клиенту __access и refresh token uuid__ (взятый из выше созданной рефреш-сессии)
    9. __access__ возвращает в теле запроса, __refresh__ устанавливает в качестве __httpOnly__ куки

    _Tip:_ Для отправки запроса с куками для `axios` есть опция `{ withCredentials: true }`
    @@ -145,7 +145,7 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    2. Закончилось время жизни __access token'на__
    3. __Клиент хакера__ отправляет __refresh token__ и __fingerprint__
    4. Сервер смотрит __fingerprint__ хакера
    5. Сервер не находит __fingerprint__ хакера в сессии и удаляет ее из БД
    5. Сервер не находит __fingerprint__ хакера в рефреш-сессии и удаляет ее из БД
    6. Сервер логирует попытку несанкционированного обновления токенов
    7. Сервер перенаправляет хакера на станицу логина. Хакер идет лесом
    8. Юзер пробует зайти на сервер >> обнаруживается что __refresh token__ отсутствует
  15. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -64,7 +64,7 @@ server {
    server_name super.com;
    # SPA/Front-end
    location / {
    try_files $uri /a/index.html;
    try_files $uri /index.html;
    root /var/www/frontend/dist;
    index index.html;
    }
  16. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -172,7 +172,7 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    - Куки в микросерисной архитектуре использовать не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают кросc-доменные запросы
    - В микросерисной архитектуре JWT позволяет каждому сервису независимо от сервера авторизации верифицировать `access` токен (через публичный ключ)
    - При использовании cookie sessions программист зачастую надеется на то, что предоставил фреймворк и оставляет как есть
    - При использовании jwt мы видим проблему с безопасностью и стараемся предусмотреть механизмы контроля в случае каржи авторизационных данных. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпрометирована и все остается как есть
    - При использовании jwt мы видим проблему с безопасностью и стараемся предусмотреть механизмы контроля в случае каржи авторизационных данных. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпрометирована
    - __На каждом запросе__ использование JWT избавляет бекенд от одного запроса в БД(или кеш) за данными пользователя(`userId`, `email`, etc.)

    ## В итоге:
  17. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -48,7 +48,7 @@ __Поскольку токены(а данном случае access) это н

    __Роль рефреш токенов и зачем их хранить в БД.__ Рефреш на сервере хранится для учета доступа и инвалидации краденых токенов. Таким образом сервер наверняка знает о клиентах которым стоит доверять(кому позволено авторизоваться). Если не хранить рефреш токен в БД то велика вероятность того что токены будут бесконтрольно гулять по рукам злоумышленников. Для отслеживания которых нам придется заводить черный список и периодически чистить его от просроченных. В место этого мы храним лимитированный список белых токенов для каждого юзера отдельно и в случае кражи у нас уже есть механизм противодействия(описано ниже).

    ## Как ставить куку ?
    ## Как ставить куки ?
    Для того что бы `refreshToken` кука была успешно уставленна и отправлена браузером, адреса эндпоинтов аутентификации(`/api/auth/login`, `/api/auth/refresh-tokens`, `/api/auth/logout`) должны располагася в доменном пространстве сайта. Вот так:
    - Адрес сайта в браузере: `super.com`
    - Домен в опциях куки: `domain: '.super.com'`
    @@ -172,7 +172,7 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    - Куки в микросерисной архитектуре использовать не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают кросc-доменные запросы
    - В микросерисной архитектуре JWT позволяет каждому сервису независимо от сервера авторизации верифицировать `access` токен (через публичный ключ)
    - При использовании cookie sessions программист зачастую надеется на то, что предоставил фреймворк и оставляет как есть
    - При использовании jwt мы видим проблему с безопасностью и стараемся предусмотреть механизмы контроля в случае каржи авторизационных данных. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть
    - При использовании jwt мы видим проблему с безопасностью и стараемся предусмотреть механизмы контроля в случае каржи авторизационных данных. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпрометирована и все остается как есть
    - __На каждом запросе__ использование JWT избавляет бекенд от одного запроса в БД(или кеш) за данными пользователя(`userId`, `email`, etc.)

    ## В итоге:
  18. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -186,7 +186,7 @@ p.s. Каждой задаче свой подход. Юзайте в небол

    ___
    ### Имплементация:
    __Front-end:__ (*покоместь без кук)
    __Front-end:__
    - https://github.com/zmts/beauty-vuejs-boilerplate/blob/master/src/services/http.init.js
    - https://github.com/zmts/beauty-vuejs-boilerplate/blob/master/src/services/auth.service.js

  19. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -54,7 +54,7 @@ __Роль рефреш токенов и зачем их хранить в БД
    - Домен в опциях куки: `domain: '.super.com'`
    - Path в опциях куки: `path: '/api/auth'`

    Таким образом кука установится в браузер и прийдет на все ендпоинты по адресу `super.com/api/auth/<any-path>`
    Таким образом кука установится в браузер и прийдет на все эндпоинты по адресу `super.com/api/auth/<any-path>`

    Если у нас монолит и за аутентификацию отвечает один и тот-же API, тут проблем не должно быть. Но если за аутентификацию отвечает отдельный микросервис, прячем его средствами `nginx` по выше указанному пути (`super.com/api/auth`).
    ```
  20. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 34 additions and 1 deletion.
    35 changes: 34 additions & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,10 @@
    # Про токены, JSON Web Tokens (JWT), аутентификацию и авторизацию. Token-Based Authentication

    `Last major update: 19.04.2020`
    `Last major update: 20.04.2020`

    - Что такое авторизация/аутентификация
    - Где хранить токены
    - Как ставить куки ?
    - Процесс логина
    - Процесс рефреш токенов
    - Кража токенов/Механизм контроля токенов
    @@ -47,6 +48,38 @@ __Поскольку токены(а данном случае access) это н

    __Роль рефреш токенов и зачем их хранить в БД.__ Рефреш на сервере хранится для учета доступа и инвалидации краденых токенов. Таким образом сервер наверняка знает о клиентах которым стоит доверять(кому позволено авторизоваться). Если не хранить рефреш токен в БД то велика вероятность того что токены будут бесконтрольно гулять по рукам злоумышленников. Для отслеживания которых нам придется заводить черный список и периодически чистить его от просроченных. В место этого мы храним лимитированный список белых токенов для каждого юзера отдельно и в случае кражи у нас уже есть механизм противодействия(описано ниже).

    ## Как ставить куку ?
    Для того что бы `refreshToken` кука была успешно уставленна и отправлена браузером, адреса эндпоинтов аутентификации(`/api/auth/login`, `/api/auth/refresh-tokens`, `/api/auth/logout`) должны располагася в доменном пространстве сайта. Вот так:
    - Адрес сайта в браузере: `super.com`
    - Домен в опциях куки: `domain: '.super.com'`
    - Path в опциях куки: `path: '/api/auth'`

    Таким образом кука установится в браузер и прийдет на все ендпоинты по адресу `super.com/api/auth/<any-path>`

    Если у нас монолит и за аутентификацию отвечает один и тот-же API, тут проблем не должно быть. Но если за аутентификацию отвечает отдельный микросервис, прячем его средствами `nginx` по выше указанному пути (`super.com/api/auth`).
    ```
    # пример настройки nginx конфига(только основые настройки)
    server {
    listen 80;
    server_name super.com;
    # SPA/Front-end
    location / {
    try_files $uri /a/index.html;
    root /var/www/frontend/dist;
    index index.html;
    }
    # Main API
    location /api {
    proxy_pass http://111.111.111.111:7000;
    }
    # Auth API
    location /api/auth {
    proxy_redirect http://222.222.222.222:7000 /auth/;
    proxy_pass http://222.222.222.222:7000;
    }
    }
    ```

    ## Логин, создание сессии/токенов (api/auth/login):
    1. Пользователь логинится в приложении, передавая логин/пароль и __fingerprint__ браузера (ну или некий иной уникальный идентификатор устройства если это не браузер)
    2. Сервер проверят подлинность логина/пароля
  21. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -139,7 +139,8 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    - Куки в микросерисной архитектуре использовать не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают кросc-доменные запросы
    - В микросерисной архитектуре JWT позволяет каждому сервису независимо от сервера авторизации верифицировать `access` токен (через публичный ключ)
    - При использовании cookie sessions программист зачастую надеется на то, что предоставил фреймворк и оставляет как есть
    - При использовании jwt мы видим проблему с безопасностью и стараемся предусмотреть механизмы контроля в случае каржи авторизационных данных. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть- __На каждом запросе__ использование JWT избавляет бекенд от одного запроса в БД(или кеш) за данными пользователя(`userId`, `email`, etc.)
    - При использовании jwt мы видим проблему с безопасностью и стараемся предусмотреть механизмы контроля в случае каржи авторизационных данных. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть
    - __На каждом запросе__ использование JWT избавляет бекенд от одного запроса в БД(или кеш) за данными пользователя(`userId`, `email`, etc.)

    ## В итоге:
    - __access__ токены храним исключительно в памяти клиентского приложения. Не в глобально доступной переменной аля `window.accessToken` а в __замыкании__
  22. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -9,7 +9,7 @@
    - Кража токенов/Механизм контроля токенов
    - Зачем все это ? JWT vs Cookie sessions

    ## Основы:
    ## Основа:
    __Аутентификация(authentication, от греч. αὐθεντικός [authentikos] – реальный, подлинный; от αὐθέντης [authentes] – автор)__ - это процесс проверки учётных данных пользователя (логин/пароль). Проверка подлинности пользователя путём сравнения введённого им логина/пароля с данными сохранёнными в базе данных.

    __Авторизация(authorization — разрешение, уполномочивание)__ - это проверка прав пользователя на доступ к определенным ресурсам.
  23. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -139,8 +139,7 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    - Куки в микросерисной архитектуре использовать не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают кросc-доменные запросы
    - В микросерисной архитектуре JWT позволяет каждому сервису независимо от сервера авторизации верифицировать `access` токен (через публичный ключ)
    - При использовании cookie sessions программист зачастую надеется на то, что предоставил фреймворк и оставляет как есть
    - При использовании jwt мы видим проблему с безопасностью и стараемся решить ее. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть
    - __На каждом запросе__ использование JWT избавляет бекенд от одного запроса в БД(или кеш) за данными пользователя(`userId`, `email`, etc.)
    - При использовании jwt мы видим проблему с безопасностью и стараемся предусмотреть механизмы контроля в случае каржи авторизационных данных. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть- __На каждом запросе__ использование JWT избавляет бекенд от одного запроса в БД(или кеш) за данными пользователя(`userId`, `email`, etc.)

    ## В итоге:
    - __access__ токены храним исключительно в памяти клиентского приложения. Не в глобально доступной переменной аля `window.accessToken` а в __замыкании__
  24. @zmts zmts revised this gist Apr 20, 2020. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -135,10 +135,12 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    ## Зачем все это ? JWT vs Cookie sessions
    Зачем этот весь геморой ? Почему не юзать старые добрые cookie sessions ? Чем не угодили куки ?
    - Куки подвержены CSRF: https://habr.com/ru/company/oleg-bunin/blog/412855 https://www.youtube.com/watch?v=x5AuK_IbJlg
    - Нативыне приложения для сматфонов не поддерживают работу с куками
    - В микросерисной архитектуре использовать куки не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают кросc-доменные запросы
    - Нативыным приложениям для сматфонов удобнее работать с токенами. Да есть хаки для работы с куки, но это не нативная поддержка
    - Куки в микросерисной архитектуре использовать не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают кросc-доменные запросы
    - В микросерисной архитектуре JWT позволяет каждому сервису независимо от сервера авторизации верифицировать `access` токен (через публичный ключ)
    - При использовании cookie sessions программист зачастую надеется на то, что предоставил фреймворк и оставляет как есть
    - При использовании jwt мы видим проблему с безопасностью и стараемся решить ее. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть
    - __На каждом запросе__ использование JWT избавляет бекенд от одного запроса в БД(или кеш) за данными пользователя(`userId`, `email`, etc.)

    ## В итоге:
    - __access__ токены храним исключительно в памяти клиентского приложения. Не в глобально доступной переменной аля `window.accessToken` а в __замыкании__
  25. @zmts zmts revised this gist Apr 19, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -119,7 +119,7 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    9. Сервер перенаправляет юзера на форму аутентификации
    10. Юзер вводит логин/пароль

    ## В случае кражи access токена, refresh куки да еще и fingerprint'а (хакер "молодец"):
    ## В случае кражи access токена, refresh куки и fingerprint'а:
    Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний.

    1. Хакер воспользовался __access token'ом__
  26. @zmts zmts revised this gist Apr 19, 2020. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -120,6 +120,8 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    10. Юзер вводит логин/пароль

    ## В случае кражи access токена, refresh куки да еще и fingerprint'а (хакер "молодец"):
    Стащить все авторизационные данные это не из легких задач, но все же допустим этот кейс как крайний.

    1. Хакер воспользовался __access token'ом__
    2. Закончилось время жизни __access token'на__
    3. __Хакер__ отправляет __refresh__ куку и __fingerprint__
  27. @zmts zmts revised this gist Apr 19, 2020. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -134,9 +134,9 @@ _Tip:_ Для отправки запроса с куками для `axios` е
    Зачем этот весь геморой ? Почему не юзать старые добрые cookie sessions ? Чем не угодили куки ?
    - Куки подвержены CSRF: https://habr.com/ru/company/oleg-bunin/blog/412855 https://www.youtube.com/watch?v=x5AuK_IbJlg
    - Нативыне приложения для сматфонов не поддерживают работу с куками
    - В микросерисной архитектуре использовать куки не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают крос-доменные запросы.
    - При использовании cookie sessions программист зачастую надеется на то, что предоставил фреймворк и оставляет как есть.
    - При использовании jwt мы видим проблему с безопасностью и стараемся решить ее. При использовании сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть.
    - В микросерисной архитектуре использовать куки не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают кросc-доменные запросы
    - При использовании cookie sessions программист зачастую надеется на то, что предоставил фреймворк и оставляет как есть
    - При использовании jwt мы видим проблему с безопасностью и стараемся решить ее. При использовании cookie сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть

    ## В итоге:
    - __access__ токены храним исключительно в памяти клиентского приложения. Не в глобально доступной переменной аля `window.accessToken` а в __замыкании__
  28. @zmts zmts revised this gist Apr 19, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -177,6 +177,7 @@ __Back-end:__
    - https://www.youtube.com/watch?v=R0-eoLp871s
    - https://www.youtube.com/watch?v=u9hn3s2kUrg
    - https://ain.ua/2020/02/29/adtech-bez-cookies/
    - https://habr.com/ru/post/492830 (cookies SameSite)

    ### And why JWT is bad
    - http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
  29. @zmts zmts revised this gist Apr 19, 2020. 1 changed file with 74 additions and 33 deletions.
    107 changes: 74 additions & 33 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -1,23 +1,30 @@
    # Про токены, JSON Web Tokens (JWT), аутентификацию и авторизацию. Token-Based Authentication

    `Last major update: 21.10.2019`
    `Last major update: 19.04.2020`

    - Что такое авторизация/аутентификация
    - Где хранить токены
    - Процесс логина
    - Процесс рефреш токенов
    - Кража токенов/Механизм контроля токенов
    - Зачем все это ? JWT vs Cookie sessions

    ## Основы:
    __Аутентификация(authentication, от греч. αὐθεντικός [authentikos] – реальный, подлинный; от αὐθέντης [authentes] – автор)__ - это процесс проверки учётных данных пользователя (логин/пароль). Проверка подлинности пользователя путём сравнения введённого им логина/пароля с данными сохранёнными в базе данных.

    __Авторизация(authorization — разрешение, уполномочивание)__ - это проверка прав пользователя на доступ к определенным ресурсам.

    Например после аутентификации юзер _**sasha**_ получает право обращатся и получать от ресурса __"super.com/vip"__ некие данные. Во время обращения юзера _**sasha**_ к ресурсу __vip__ система авторизации проверит имеет ли право юзер обращатся к этому ресурсу (проще говоря переходить по неким разрешенным ссылкам)
    Например, после аутентификации юзер _**sasha**_ получает право обращаться и получать от ресурса __"super.com/vip"__ некие данные. Во время обращения юзера _**sasha**_ к ресурсу __vip__ система авторизации проверит имеет ли право юзер обращаться к этому ресурсу (проще говоря переходить по неким разрешенным ссылкам)

    1. Юзер c емайлом _**sasha_gmail.com**_ успешно прошел аутентификацию
    2. Сервер посмотрел в БД какая роль у юзера
    3. Сервер сгенерил юзеру токен с указанной ролью
    4. Юзер заходит на некий ресурс используя полученный токен
    5. Сервер смотрит на права(роль) юзера в токене и соотвественно пропускает или отсекает запрос
    5. Сервер смотрит на права(роль) юзера в токене и соответственно пропускает или отсекает запрос

    Собственно п.5 и есть процесс __авторизации__.

    *Дабы не путатся с понятиями __Authentication/Authorization__ можно использовать псевдонимы __checkPassword/checkAccess__(я так сделал в своей API)*
    *Дабы не путаться с понятиями __Authentication/Authorization__ можно использовать псевдонимы __checkPassword/checkAccess__(я так сделал в своей API)*

    __JSON Web Token (JWT)__ — содержит три блока, разделенных точками: заголовок(__header__), набор полей (__payload__) и __сигнатуру__. Первые два блока представлены в JSON-формате и дополнительно закодированы в формат base64. Набор полей содержит произвольные пары имя/значения, притом стандарт JWT определяет несколько зарезервированных имен (iss, aud, exp и другие). Сигнатура может генерироваться при помощи и симметричных алгоритмов шифрования, и асимметричных. Кроме того, существует отдельный стандарт, отписывающий формат зашифрованного JWT-токена.

    @@ -26,44 +33,46 @@ __JSON Web Token (JWT)__ — содержит три блока, разделе
    { alg: "HS256", typ: "JWT" }.{ iss: "auth.myservice.com", aud: "myservice.com", exp: 1435937883, userName: "John Smith", userRole: "Admin" }.S9Zs/8/uEGGTVVtLggFTizCsMtwOJnRhjaQ2BMUQhcY
    ```

    __Токены__ предоставляют собой средство __авторизации__ для каждого запроса от клиента к серверу. Токены(и соотвественно сигнатура токена) генерируются на сервере основываясь на секретном ключе(который хранится на сервере) и __payload'e__. Токен в итоге хранится на клиенте и используется при необходимости __авторизации__ како-го либо запроса. Такое решение отлично подходит при разработке SPA.
    __Токены__ предоставляют собой средство __авторизации__ для каждого запроса от клиента к серверу. Токены(и соответственно сигнатура токена) генерируются на сервере основываясь на секретном ключе(который хранится на сервере) и __payload'e__. Токен в итоге хранится на клиенте и используется при необходимости __авторизации__ какого-либо запроса. Такое решение отлично подходит при разработке SPA.

    При попытке хакером подменить данные в __header'ре__ или __payload'е__, токен cтанет не валидным, поскольку сигнатура не будет соответствовать изначальным значениям. А возможность сгенерировать новую сигнатуру у хакера отсутствует, поскольку секретный ключ для зашифровки лежит на сервере.
    При попытке хакером подменить данные в __header'ре__ или __payload'е__, токен станет не валидным, поскольку сигнатура не будет соответствовать изначальным значениям. А возможность сгенерировать новую сигнатуру у хакера отсутствует, поскольку секретный ключ для зашифровки лежит на сервере.

    __access token__ - используется для __авторизации запросов__ и хранения дополнительной информации о пользователе (аля __user_id__, __user_role__ или еще что либо, эту информацию также называет __payload__). Сам токен храним не в localStorage как это обычно делают, а __в памяти клиентского приложения.__
    __access token__ - используется для __авторизации запросов__ и хранения дополнительной информации о пользователе (аля __user_id__, __user_role__ или еще что либо, эту информацию также называет __payload__). __Сам токен храним не в localStorage как это обычно делают, а в памяти клиентского приложения.__

    __refresh token__ - выдается сервером по результам успешной аутентификации и используется для получения новой пары __access/refresh__ токенов. Храним в любом персистентном хранилище.
    __refresh token__ - выдается сервером по результам успешной аутентификации и используется для получения новой пары __access/refresh__ токенов. __Храним исключительно в httpOnly куке__.

    Каждый токен имеет свой срок жизни, например __access__: 30 мин, __refresh__: 60 дней

    __Поскольку токены это не зашифрованная информация крайне не рекомендуется хранить в них какую либо `sensitive data` (passwords, payment credentials, etc...)__
    __Поскольку токены(а данном случае access) это не зашифрованная информация крайне не рекомендуется хранить в них какую либо `sensitive data` (passwords, payment credentials, etc...)__

    __Роль рефреш токенов и зачем их хранить в БД.__ Рефреш на сервере хранится для учета доступа и инвалидации краденых токенов. Таким образом сервер наверняка знает о клиентах которым стоит доверять(кому позволено авторизоваться). Если не хранить рефреш токен в БД то велика вероятность того что токены будут бесконтрольно гулять по рукам злоумышленников. Для отслеживания которых нам прийдется заводить черный список и периодически чистить его от просроченных. В место этого мы храним лимитированный список белых токенов для каждого юзера отдельно и в случае кражи у нас уже есть механизм противодействия(описано ниже).
    __Роль рефреш токенов и зачем их хранить в БД.__ Рефреш на сервере хранится для учета доступа и инвалидации краденых токенов. Таким образом сервер наверняка знает о клиентах которым стоит доверять(кому позволено авторизоваться). Если не хранить рефреш токен в БД то велика вероятность того что токены будут бесконтрольно гулять по рукам злоумышленников. Для отслеживания которых нам придется заводить черный список и периодически чистить его от просроченных. В место этого мы храним лимитированный список белых токенов для каждого юзера отдельно и в случае кражи у нас уже есть механизм противодействия(описано ниже).

    ## Логин, создание сессии/токенов (api/auth/login):
    1. Пользователь логинится в приложении, передавая логин/пароль и __fingerprint__ браузера (ну или некий иной уникальный индентификатор устройства если это не браузер)
    2. Сервер проверят подлинность логина/пароля,
    1. Пользователь логинится в приложении, передавая логин/пароль и __fingerprint__ браузера (ну или некий иной уникальный идентификатор устройства если это не браузер)
    2. Сервер проверят подлинность логина/пароля
    3. В случае удачи создает и записывает сессию в БД `{ userId: uuid, refreshToken: uuid, expiresIn: int, fingerprint: string, ... }` (схема таблицы ниже)
    4. Отправляет клиенту два токена __access и refresh token uuid__ (взятый из выше созданной сессии)
    ```
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "refreshToken": "9f34dd3a-ff8d-43aa-b286-9f22555319f6"
    ```
    5. Клиент сохраняет токены(__access__ в памяти приложения, __refresh__ персистентно), используя __access token__ для последующей авторизации запросов.
    4. Создает __access token__
    5. Отправляет клиенту __access и refresh token uuid__ (взятый из выше созданной сессии)
    6. __access__ возвращает в теле запроса, __refresh__ устанавливает в качестве __httpOnly__ куки
    7. Клиент сохраняет токены(__access__ в памяти приложения, __refresh__ сетится как кука автоматом)

    На что нужно обратить внимание при установке __refresh__ куки:
    - `maxAge` куки ставим равную `expiresIn` из выше созданной сессии
    - В `path` ставим корневой роут `auth` контроллера (`/api/auth`) это важно, таким образом токен получат только те хендлеры которым он нужен(`/api/auth/logout` и `/api/auth/rerfesh-tokens`), остальные обойдутся(нечего зря почём отправлять __sensitive data__).

    __Стоит заметить что процесс добавления сессии в таблицу должен имеет свои меры безопасности.__ При добавлении стоит проверять сколько сессий всего есть у юзера и если их слишком много или юзер конектится одновременно из нескольких подсетей, стоит предпринять меры. Имплементируя данную проверку я проверяю только что бы юзер имел максимум до 5 одновременных сессий максимум, и на 6'ой удаляю все остальные сессии кроме текущей(6'ой). Все остальные проверки на ваше усмотрение в зависимости от задачи.
    __Стоит заметить, что процесс добавления сессии в таблицу должен имеет свои меры безопасности.__ При добавлении стоит проверять сколько сессий всего есть у юзера и, если их слишком много или юзер конектится одновременно из нескольких подсетей, стоит предпринять меры. Имплементируя данную проверку, я проверяю только что бы юзер имел максимум до 5 одновременных сессий максимум, и на 6'ой удаляю все остальные сессии кроме текущей(6'ой). Все остальные проверки на ваше усмотрение в зависимости от задачи.

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

    Перед каждым запросом клиент предварительно проверяет время жизни __access token'а__ (да берем `expires_in` прямо из JWT в клиентском приложении) и если оно истекло использует __refresh token__ чтобы обновить __ОБА__ токена и продолжает использовать новый __access token__. Для большей уверенности можем обновлять токены на несколько секунд раньше.
    Перед каждым запросом клиент предварительно проверяет время жизни __access token'а__ (да берем `expiresIn` прямо из JWT в клиентском приложении) и если оно истекло шлет запрос на обновление токенов. Для большей уверенности можем обновлять токены на несколько секунд раньше. То есть кейс когда API получит истекший __access__ токен практически исключен.

    Что такое __fingerprint__ ? Это инструмент отслеживания браузера вне зависимости от желания пользователя быть идентифицированным. Это хеш сгенерированный js'ом на базе неких уникальных параметров/компонентов браузера. Преимущество __fingerprint'a__ в том что он нигде персистентно не хранится и генерируется только в момент логина и рефреша.
    - Библиотека для хеширования: https://github.com/Valve/fingerprintjs2
    - Более подробно: https://player.vimeo.com/video/151208427
    - Пример ф-ции получения такого хеша: https://gist.github.com/zmts/b26ba9a61aa0b93126fc6979e7338ca3

    ## Рефреш токенов (api/auth/refresh-tokens):
    Для использования возможности аутентификации на более чем одном девайсе необходимо хранить все рефреш токены по каждому юзеру. Я храню это список в PostgreSQL таблице. В процессе каждого логина создается запись с IP/Fingerprint и другой мета информацией то есть сессия.
    Для использования возможности аутентификации на более чем одном девайсе необходимо хранить все рефреш токены по каждому юзеру. Я храню это список в PostgreSQL таблице(а надо бы в Redis'е). В процессе каждого логина создается запись с IP/Fingerprint и другой мета информацией, то есть сессия.
    ```
    CREATE TABLE sessions (
    "id" SERIAL PRIMARY KEY,
    @@ -78,37 +87,69 @@ CREATE TABLE sessions (
    ```

    1. Клиент(фронтенд) проверяет перед запросом не истекло ли время жизни __access token'на__
    2. Если истекло клиент отправляет на `auth/refresh-token` `{ refreshToken: uuid, fingerprint: string }`
    2. Если истекло клиент делает запрос на `POST auth/refresh-tokens` `{ fingerprint: string }` в `body` и соответственно `refreshToken` куку.
    3. Сервер получает запись сессии по UUID'у рефреш токена
    4. Cохраняет текущую сессию в переменную и удаляет ее из таблицы
    4. Сохраняет текущую сессию в переменную и удаляет ее из таблицы
    5. Проверяет текущую сессию:
    1. Не истекло ли время жизни
    2. На соответствие старого __fingerprint'a__ полученного из текущей сессии с новым полученным из тела запроса
    6. В случае негативного результата бросает ошибку `TOKEN_EXPIRED`/`INVALID_SESSION`
    7. В случае успеха создает новую сессию и записывает ее в БД
    8. Создает новый __access token__
    9. Отправляет клиенту `{ accessToken, refreshToken }`
    8. Создает __access token__
    8. Отправляет клиенту __access и refresh token uuid__ (взятый из выше созданной сессии)
    9. __access__ возвращает в теле запроса, __refresh__ устанавливает в качестве __httpOnly__ куки

    _Tip:_ Для отправки запроса с куками для `axios` есть опция `{ withCredentials: true }`

    ## Ключевой момент:
    В момент рефреша то есть обновления __access token'a__ обновляются __ОБА__ токена. Но как же __refresh token__ может сам себя обновить, он ведь создается только после успешной аунтефикации ? __refresh token__ в момент рефреша сравнивает себя с тем __refresh token'ом__ который лежит в БД и вслучае успеха, а также если у него не истек срок, система рефрешит токены.

    Вопрос зачем __refresh token'y__ срок жизни, если он обновляется каждый раз при обновлении __access token'a__ ? Это сделано на случай если юзер будет в офлайне более 60 дней, тогда прийдется заново вбить логин/пароль.
    Вопрос зачем __refresh token'y__ срок жизни, если он обновляется каждый раз при обновлении __access token'a__ ? Это сделано на случай, если юзер будет в офлайне более 60 дней, тогда придется заново вбить логин/пароль.


    ## В случае кражи токенов:
    ## В случае кражи access токена и refresh куки:
    1. Хакер воспользовался __access token'ом__
    2. Закончилось время жизни __access token'на__
    3. __Клиент хакера__ отправляет __refresh token__ и __fingerprint__ (если знает что он нужен)
    4. Сервер смотрит __fingerprint__ хакера (если он есть в запросе)
    3. __Клиент хакера__ отправляет __refresh token__ и __fingerprint__
    4. Сервер смотрит __fingerprint__ хакера
    5. Сервер не находит __fingerprint__ хакера в сессии и удаляет ее из БД
    6. Сервер логирует попытку несанкционированного обновления токенов
    7. Сервер перенаправляет хакера на станицу логина. Хакер идет лесом
    8. Юзер пробует зайти на сервер >> обнаруживается что __refresh token__ отсутствует
    9. Сервер перенаправляет юзера на форму аутентификации
    10. Юзер вводит логин/пароль

    ### Пример имплементации:
    __Front-end:__
    ## В случае кражи access токена, refresh куки да еще и fingerprint'а (хакер "молодец"):
    1. Хакер воспользовался __access token'ом__
    2. Закончилось время жизни __access token'на__
    3. __Хакер__ отправляет __refresh__ куку и __fingerprint__
    4. На сервере создается новый __refresh__ токен ("от хакера")
    5. Хакер получает новую пару токенов
    6. Юзер пробует отправить запрос на сервер >> обнаруживается что __refresh__ токен не валиден
    7. Сервер перенаправляет юзера на форму аутентификации
    8. Юзер вводит логин/пароль
    9. Создается новый __refresh__ токен >> __refresh__ токен "от хакера" становится не валиден

    ## Зачем все это ? JWT vs Cookie sessions
    Зачем этот весь геморой ? Почему не юзать старые добрые cookie sessions ? Чем не угодили куки ?
    - Куки подвержены CSRF: https://habr.com/ru/company/oleg-bunin/blog/412855 https://www.youtube.com/watch?v=x5AuK_IbJlg
    - Нативыне приложения для сматфонов не поддерживают работу с куками
    - В микросерисной архитектуре использовать куки не вариант. Напомню зачастую микросервисы раскиданы на разных доменах, а куки не поддерживают крос-доменные запросы.
    - При использовании cookie sessions программист зачастую надеется на то, что предоставил фреймворк и оставляет как есть.
    - При использовании jwt мы видим проблему с безопасностью и стараемся решить ее. При использовании сессий программист зачастую даже не задумывается что сессия может быть скомпонована и все остается как есть.

    ## В итоге:
    - __access__ токены храним исключительно в памяти клиентского приложения. Не в глобально доступной переменной аля `window.accessToken` а в __замыкании__
    - __refresh__ токен храним исключительно в __httpOnly__ куке
    - Механизмы контроля при угоне __sensitive data__ в наличии
    - Взяли лучшее из обеих технологий, максимально обезопасились от CSRF/XSS
    - Добавьте в компанию ко всему CSP заголовки и SameSite=Strict флаг для кук и ждите прихода злодеев

    p.s. Каждой задаче свой подход. Юзайте в небольших/средних монолитах `cookie sessions` и не парьтесь. Ну или на ваш вкус :)

    ___
    ### Имплементация:
    __Front-end:__ (*покоместь без кук)
    - https://github.com/zmts/beauty-vuejs-boilerplate/blob/master/src/services/http.init.js
    - https://github.com/zmts/beauty-vuejs-boilerplate/blob/master/src/services/auth.service.js

    @@ -145,5 +186,5 @@ __Back-end:__
    - https://scotch.io/bar-talk/why-jwts-suck-as-session-tokens
    - https://t.me/why_jwt_is_bad

    p.s.
    Комментарии периодически подчищаются
    ---
    _Комментарии периодически подчищаются_
  30. @zmts zmts revised this gist Apr 19, 2020. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -116,6 +116,7 @@ __Back-end:__
    - https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth

    ### Info:
    - https://www.youtube.com/playlist?list=PLvTBThJr861y60LQrUGpJNPu3Nt2EeQsP
    - https://habrahabr.ru/company/Voximplant/blog/323160/
    - https://tools.ietf.org/html/rfc6749
    - https://www.digitalocean.com/community/tutorials/oauth-2-ru
    @@ -132,7 +133,6 @@ __Back-end:__
    - https://www.digitalocean.com/community/tutorials/oauth-2-ru
    - https://github.com/shieldfy/API-Security-Checklist/blob/master/README-ru.md
    - https://www.youtube.com/watch?v=Ngh3KZcGNaU
    - https://www.youtube.com/playlist?list=PLvTBThJr861y60LQrUGpJNPu3Nt2EeQsP
    - https://www.youtube.com/watch?v=R0-eoLp871s
    - https://www.youtube.com/watch?v=u9hn3s2kUrg
    - https://ain.ua/2020/02/29/adtech-bez-cookies/
    @@ -144,3 +144,6 @@ __Back-end:__
    - https://medium.com/@cjainn/anatomy-of-a-jwt-token-part-2-c12888abc1a2
    - https://scotch.io/bar-talk/why-jwts-suck-as-session-tokens
    - https://t.me/why_jwt_is_bad

    p.s.
    Комментарии периодически подчищаются