Skip to content

Instantly share code, notes, and snippets.

@RomanRubachonok
Forked from zmts/tokens.md
Created April 29, 2020 15:43
Show Gist options
  • Save RomanRubachonok/faf6158f0e15f43fa59c35ad7d24996b to your computer and use it in GitHub Desktop.
Save RomanRubachonok/faf6158f0e15f43fa59c35ad7d24996b to your computer and use it in GitHub Desktop.

Revisions

  1. @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'ти устройствах) система сбросит все сессии(рефреш токены) кроме последней.

  2. @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>`

  3. @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__ отсутствует
  4. @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;
    }
  5. @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.)

    ## В итоге:
  6. @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.)

    ## В итоге:
  7. @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

  8. @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`).
    ```
  9. @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. Сервер проверят подлинность логина/пароля
  10. @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` а в __замыкании__
  11. @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 — разрешение, уполномочивание)__ - это проверка прав пользователя на доступ к определенным ресурсам.
  12. @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` а в __замыкании__
  13. @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` а в __замыкании__
  14. @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'ом__
  15. @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__
  16. @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` а в __замыкании__
  17. @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/
  18. @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.
    Комментарии периодически подчищаются
    ---
    _Комментарии периодически подчищаются_
  19. @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.
    Комментарии периодически подчищаются
  20. @zmts zmts revised this gist Mar 30, 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
    @@ -73,8 +73,7 @@ CREATE TABLE sessions (
    "fingerprint" character varying(200) NOT NULL,
    "ip" character varying(15) NOT NULL,
    "expiresIn" bigint NOT NULL,
    "createdAt" timestamp with time zone NOT NULL DEFAULT now(),
    "updatedAt" timestamp with time zone NOT NULL DEFAULT now()
    "createdAt" timestamp with time zone NOT NULL DEFAULT now()
    );
    ```

  21. @zmts zmts revised this gist Mar 25, 2020. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -43,14 +43,18 @@ __Роль рефреш токенов и зачем их хранить в БД
    ## Логин, создание сессии/токенов (api/auth/login):
    1. Пользователь логинится в приложении, передавая логин/пароль и __fingerprint__ браузера (ну или некий иной уникальный индентификатор устройства если это не браузер)
    2. Сервер проверят подлинность логина/пароля,
    3. В случае удачи создает и записывает сессию в БД `{ userId: uuid, refreshToken: uuid, expiresIn: int, fingerprint: string, ... }`
    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__ для последующей авторизации запросов.

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

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

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

    Что такое __fingerprint__ ? Это инструмент отслеживания браузера вне зависимости от желания пользователя быть идентифицированным. Это хеш сгенерированный js'ом на базе неких уникальных параметров/компонентов браузера. Преимущество __fingerprint'a__ в том что он нигде персистентно не хранится и генерируется только в момент логина и рефреша.
    @@ -86,10 +90,6 @@ CREATE TABLE sessions (
    8. Создает новый __access token__
    9. Отправляет клиенту `{ accessToken, refreshToken }`

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

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

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

  22. @zmts zmts revised this gist Mar 1, 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
    @@ -136,6 +136,7 @@ __Back-end:__
    - 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/

    ### And why JWT is bad
    - http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
  23. @zmts zmts revised this gist Dec 10, 2019. 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
    @@ -117,7 +117,7 @@ __Back-end:__
    - https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth

    ### Info:
    - Заметка базируется на: https://habrahabr.ru/company/Voximplant/blog/323160/
    - https://habrahabr.ru/company/Voximplant/blog/323160/
    - https://tools.ietf.org/html/rfc6749
    - https://www.digitalocean.com/community/tutorials/oauth-2-ru
    - https://jwt.io/introduction/
  24. @zmts zmts revised this gist Dec 10, 2019. 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
    @@ -79,8 +79,8 @@ CREATE TABLE sessions (
    3. Сервер получает запись сессии по UUID'у рефреш токена
    4. Cохраняет текущую сессию в переменную и удаляет ее из таблицы
    5. Проверяет текущую сессию:
    5.1. Не истекло ли время жизни
    5.2. На соответствие старого __fingerprint'a__ полученного из текущей сессии с новым полученным из тела запроса
    1. Не истекло ли время жизни
    2. На соответствие старого __fingerprint'a__ полученного из текущей сессии с новым полученным из тела запроса
    6. В случае негативного результата бросает ошибку `TOKEN_EXPIRED`/`INVALID_SESSION`
    7. В случае успеха создает новую сессию и записывает ее в БД
    8. Создает новый __access token__
  25. @zmts zmts revised this gist Dec 10, 2019. 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
    @@ -79,8 +79,8 @@ CREATE TABLE sessions (
    3. Сервер получает запись сессии по UUID'у рефреш токена
    4. Cохраняет текущую сессию в переменную и удаляет ее из таблицы
    5. Проверяет текущую сессию:
    5.1 Не истекло ли время жизни
    5.1 На соответствие старого __fingerprint'a__ полученного из текущей сессии с новым полученным из тела запроса
    5.1. Не истекло ли время жизни
    5.2. На соответствие старого __fingerprint'a__ полученного из текущей сессии с новым полученным из тела запроса
    6. В случае негативного результата бросает ошибку `TOKEN_EXPIRED`/`INVALID_SESSION`
    7. В случае успеха создает новую сессию и записывает ее в БД
    8. Создает новый __access token__
  26. @zmts zmts revised this gist Dec 10, 2019. 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
    @@ -79,8 +79,8 @@ CREATE TABLE sessions (
    3. Сервер получает запись сессии по UUID'у рефреш токена
    4. Cохраняет текущую сессию в переменную и удаляет ее из таблицы
    5. Проверяет текущую сессию:
    5.1 Не истекло ли время жизни
    5.1 На соответствие старого __fingerprint'a__ полученного из текущей сессии с новым полученным из тела запроса
    5.1 Не истекло ли время жизни
    5.1 На соответствие старого __fingerprint'a__ полученного из текущей сессии с новым полученным из тела запроса
    6. В случае негативного результата бросает ошибку `TOKEN_EXPIRED`/`INVALID_SESSION`
    7. В случае успеха создает новую сессию и записывает ее в БД
    8. Создает новый __access token__
  27. @zmts zmts revised this gist Nov 9, 2019. 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
    @@ -100,7 +100,7 @@ CREATE TABLE sessions (
    1. Хакер воспользовался __access token'ом__
    2. Закончилось время жизни __access token'на__
    3. __Клиент хакера__ отправляет __refresh token__ и __fingerprint__ (если знает что он нужен)
    4. Сервер смотрит __fingerprint__ хакера
    4. Сервер смотрит __fingerprint__ хакера (если он есть в запросе)
    5. Сервер не находит __fingerprint__ хакера в сессии и удаляет ее из БД
    6. Сервер логирует попытку несанкционированного обновления токенов
    7. Сервер перенаправляет хакера на станицу логина. Хакер идет лесом
  28. @zmts zmts revised this gist Nov 4, 2019. 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
    @@ -116,7 +116,7 @@ __Front-end:__
    __Back-end:__
    - https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth

    ### Чтиво:
    ### Info:
    - Заметка базируется на: https://habrahabr.ru/company/Voximplant/blog/323160/
    - https://tools.ietf.org/html/rfc6749
    - https://www.digitalocean.com/community/tutorials/oauth-2-ru
  29. @zmts zmts revised this gist Nov 4, 2019. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -132,7 +132,6 @@ __Back-end:__
    - https://egghead.io/courses/json-web-token-jwt-authentication-with-node-js
    - 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
  30. @zmts zmts revised this gist Nov 4, 2019. 1 changed file with 5 additions and 2 deletions.
    7 changes: 5 additions & 2 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -129,12 +129,15 @@ __Back-end:__
    - https://habr.com/post/340146/
    - https://habr.com/company/mailru/blog/115163/
    - https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
    - https://www.youtube.com/watch?v=Ngh3KZcGNaU
    - https://www.youtube.com/playlist?list=PLvTBThJr861y60LQrUGpJNPu3Nt2EeQsP
    - https://egghead.io/courses/json-web-token-jwt-authentication-with-node-js
    - 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

    ### And why JWT is bad
    - http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
    - http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/