Skip to content

Instantly share code, notes, and snippets.

@bger
Forked from zmts/tokens.md
Last active November 26, 2018 22:17
Show Gist options
  • Save bger/9d71c4382da9f789a39bf4d9b7a0eb4f to your computer and use it in GitHub Desktop.
Save bger/9d71c4382da9f789a39bf4d9b7a0eb4f to your computer and use it in GitHub Desktop.

Revisions

  1. bger revised this gist Nov 26, 2018. 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
    @@ -149,6 +149,7 @@ __Back-end:__ https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth


    Table "public.sessions"
    Column | Type | Modifiers | Storage | Stats target | Description
    ------------+-----------------------------+-------------------------------------------------------+----------+--------------+-------------
    id | bigint | not null default nextval('sessions_id_seq'::regclass) | plain | |
  2. bger revised this gist Nov 26, 2018. 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
    @@ -147,6 +147,7 @@ __Back-end:__ https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth

    Вот примерная структура в базе данных


    Table "public.sessions"
    Column | Type | Modifiers | Storage | Stats target | Description
    ------------+-----------------------------+-------------------------------------------------------+----------+--------------+-------------
    @@ -160,6 +161,7 @@ __Back-end:__ https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth
    expired_at | timestamp without time zone | not null | plain | |
    created_at | timestamp without time zone | not null | plain | |
    updated_at | timestamp without time zone | not null | plain | |

    в ней хранятся refresh token-ы которые представляют из себя любую случайную уникальную строку, например 763675e5-f22a-4a51-bf9f-8ee784a1d500, различные данные об устройстве и expired_at (дата истечения refresh token-а)

    Авторизация
  3. bger revised this gist Nov 26, 2018. 1 changed file with 94 additions and 0 deletions.
    94 changes: 94 additions & 0 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -142,3 +142,97 @@ __Back-end:__ https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth
    - https://www.digitalocean.com/community/tutorials/oauth-2-ru


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

    Вот примерная структура в базе данных

    Table "public.sessions"
    Column | Type | Modifiers | Storage | Stats target | Description
    ------------+-----------------------------+-------------------------------------------------------+----------+--------------+-------------
    id | bigint | not null default nextval('sessions_id_seq'::regclass) | plain | |
    user_id | bigint | not null | plain | |
    ip | cidr | not null | main | |
    os | text | | extended | |
    browser | text | | extended | |
    user_agent | text | | extended | |
    token | character varying(36) | not null | extended | |
    expired_at | timestamp without time zone | not null | plain | |
    created_at | timestamp without time zone | not null | plain | |
    updated_at | timestamp without time zone | not null | plain | |
    в ней хранятся refresh token-ы которые представляют из себя любую случайную уникальную строку, например 763675e5-f22a-4a51-bf9f-8ee784a1d500, различные данные об устройстве и expired_at (дата истечения refresh token-а)

    Авторизация

    Начну с простого (в надежде что кто то поймет меня, а то из меня такой себе писатель), пользователь ввел корректный логин/пароль и мы создаем запись в таблице, например такую:

    -[ RECORD 1 ]--------------------------------------------------------------------------------------------------------------------------------------------
    id | 677
    user_id | 96
    ip | ::1/128
    os | Mac 10.13.4
    browser | Safari 11.1
    user_agent | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15
    token | 6d36877d-6a5d-411d-85f7-9d68b37f6761
    expired_at | 2018-06-19 09:21:34.099956
    created_at | 2018-06-18 09:21:34.10338
    updated_at | 2018-06-18 09:21:34.10338
    Далее формируем ему JWT access token, который состоит например из:
    (sub: user.id, jti: refresh_token, roles: user.roles) # refresh_token - это тот что выше из базы

    и отправляем его клиенту тот в свою очередь записывает данный токен например в куку.

    Работа на клиенте происходит следующим образом

    Далее происходит интересное. Имеем клиент у которого есть кука с JWT токеном, теперь с каждым запросом мы:

    отправляем JWT токен в заголовке запроса (не куку, это важно), например HTTP_AUTHORIZATION
    с каждым запросом мы будем получать новый JWT, как он генерируется я опишу ниже. Но суть в том что мы записываем токен который к нам пришел в ответе и заменяем им старый, заголовок может называться как MyProject-Access-Token
    Мы используем GraphQL и React.js с apollo-graphql на борту, так что если у кого похожий стэк, могу написать как это сделать на клиенте.

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

    Что происходит на сервере

    Представим ситуацию что мы получили запрос на получение данный и JWT access token в заголовке запроса.

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

    И так, есть JWT, в которым первым делом мы проверяем его действительность, то что он подписан нашим ключом. Далее смотрим время его окончания, и тут 2 возможных исхода, опишу их подробнее.

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

    В случае если время вышло, тут происходит интересное, мы делаем запрос к базе данных и просим вернуть строку в которой token == JWT.refresh_token (т.е. ищем строку ту самую что выше с нашим токеном, но не по id пользователя). В данной строке есть поле expired_at, в нем указанно время окончания refresh токена и если время вышло, значит данный токен не рабочий, можете считать что его просто не существует и возвращать пользователя на страницу авторизации. Если время все таки старше текущей даты (т.е. refresh токен актуальный), в таком случае есть 2 решения, объясню самый простой (а то и так много текста). Мы просто изменяем дату окончания, допустим на "текущее время + Х дней".

    Как результат и в первом и втором случае мы формируем новый access JWT token с новой датой окончания.

    вот схема работы что я обещал выше: https://ibb.co/hZ7mhJ

    ладно, идем дальше, по ходу дела постараюсь ответить на ваши вопросы...

    Безопасность

    - Я потерял устройство с которого можно попасть в аккаунт...

    У нас в системе была возможность у пользователя завершить сессию на Х устройстве или выйти из всех сессий кроме текущей. Как это работало, опять же, простыми словами (некоторые очевидные вещи я не озвучиваю):

    мы получаем запрос с ID сессией из которой надо выйти
    обновляем строку и устанавливаем expired_at в значение текущего времени
    аналогично обновляем список с массивом айдишников если нужно выйти из всех сервисов.
    Но тут возникает проблема что злоумышленник у которого будет access токен, сможет им еще пользоваться до его окончания, а это может печально закончится, что бы этого не допустить, следующим шагом мы записываем в Redis индефикаторы refresh токенов которые заблокировал пользователь и устанавливаем эту запись на время access токена таким образом если access токен живет 10 минут, то и в редис мы вешаем блокировку на 10 минут.

    - У меня украли куки с устройства

    Тут 2 основных момента безопасности, первый заключается в том что когда человек нажимает кнопку "Выход" мы refresh token делаем не действительны т.е. баним его и меняем expired_at. Естественно кнопку выход никто не нажимает, по этому второй момент заключается в том на сколько вы хотите заморочиться, так как любые дополнительные ограничения накладывают и сложности для клиентов. Например

    вы можете писать в access токен IP адрес пользователя и проверять его при каждом запросе и если он изменился просить пользователя ввести только пароль. Аналогичным образом можно мониторить user agent и все тому подобное.

    вы можете указать refresh токен в рамках Х часов, и если это время сервисом не пользовались то пользователь вылетит.

    вы можете контролировать что бы использовалось всегда только одно устройство

    Проблемы от подобных вещей я думаю ясны всем.... Мы остановились на том что если у Х пользователя изменился IP и User Agent мы просим ввести его пароль, если происходят другие странные изменения мы просто уведомляем его, например на почту.

    Писал просто свои мысли о JWT, многое не сказал, возможно где то опечатывался или что то забывал добавить, замечу поправлю. Надеюсь кому то будет полезно.


  4. @zmts zmts revised this gist Nov 19, 2018. 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
    @@ -139,5 +139,6 @@ __Back-end:__ https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth
    - 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


  5. @zmts zmts revised this gist Nov 19, 2018. 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
    @@ -132,8 +132,9 @@ __Back-end:__ https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth
    - https://auth0.com/blog/cookies-vs-tokens-definitive-guide/
    - https://auth0.com/blog/ten-things-you-should-know-about-tokens-and-cookies/
    - https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
    - https://habrahabr.ru/company/dataart/blog/262817/
    - https://habr.com/company/dataart/blog/262817/
    - 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
  6. @zmts zmts revised this gist Oct 12, 2018. 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
    @@ -5,7 +5,7 @@

    ## Основы:

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

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

  7. @zmts zmts revised this gist Oct 6, 2018. 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
    @@ -40,7 +40,7 @@ __refresh token__ - выдается сервером по результам у

    __Поскольку токены это не зашифрованная информация крайне не рекомендуется хранить в них такую информацию как пароли.__

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

    ## Схема создания/использования токенов (api/auth/login):
    1. Пользователь логинится в приложении, передавая логин/пароль на сервер
  8. @zmts zmts revised this gist Oct 5, 2018. 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
    @@ -40,7 +40,7 @@ __refresh token__ - выдается сервером по результам у

    __Поскольку токены это не зашифрованная информация крайне не рекомендуется хранить в них такую информацию как пароли.__

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

    ## Схема создания/использования токенов (api/auth/login):
    1. Пользователь логинится в приложении, передавая логин/пароль на сервер
  9. @zmts zmts revised this gist Oct 5, 2018. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion tokens.md
    Original file line number Diff line number Diff line change
    @@ -38,7 +38,9 @@ __refresh token__ - выдается сервером по результам у

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

    __Поскольку токены это не зашифрованная информация крайне не рекомендуется хранить в них такую информацию как пароли__
    __Поскольку токены это не зашифрованная информация крайне не рекомендуется хранить в них такую информацию как пароли.__

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

    ## Схема создания/использования токенов (api/auth/login):
    1. Пользователь логинится в приложении, передавая логин/пароль на сервер
  10. @zmts zmts revised this gist Oct 3, 2018. 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
    @@ -73,7 +73,7 @@ __Если рассматривать возможность аутентифи
    -------------------------------------------------------------------------------------------------
    | id | username | refreshTokensMap
    -------------------------------------------------------------------------------------------------
    | 1 | alex | { refreshTokenId1: 'refreshTokenBody1', refreshTokenId2: 'refreshTokenBody2'}
    | 1 | alex | { refreshTokenTimestamp1: 'refreshTokenBody1', refreshTokenTimestamp2: 'refreshTokenBody2'}
    -------------------------------------------------------------------------------------------------
    ```
    1. Клиент проверяет перед запросом не истекло ли время жизни __access token'на__
  11. @zmts zmts revised this gist Oct 3, 2018. 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
    @@ -25,7 +25,7 @@ __JSON Web Token (JWT)__ — содержит три блока, разделе

    Пример подписанного JWT токена (после декодирования 1 и 2 блоков):
    ```
    { «alg»: «HS256», «typ»: «JWT» }.{ «iss»: «auth.myservice.com», «aud»: «myservice.com», «exp»: «1435937883», «userName»: «John Smith», «userRole»: «Admin» }.S9Zs/8/uEGGTVVtLggFTizCsMtwOJnRhjaQ2BMUQhcY
    { alg: "HS256", typ: "JWT" }.{ iss: "auth.myservice.com", aud: "myservice.com", exp: 1435937883, userName: "John Smith", userRole: "Admin" }.S9Zs/8/uEGGTVVtLggFTizCsMtwOJnRhjaQ2BMUQhcY
    ```

    __Токены__ предоставляют собой средство __авторизации__ для каждого запроса от клиента к серверу. Токены(и соотвественно сигнатура токена) генерируются на сервере основываясь на секретном ключе(который хранится на сервере) и __payload'e__. Токен в итоге хранится на клиенте и используется при необходимости __авторизации__ како-го либо запроса. Такое решение отлично подходит при разработке SPA.
  12. @zmts zmts revised this gist Sep 13, 2018. 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
    @@ -115,6 +115,7 @@ __Проблема:__ Поскольку __refresh token__ продлевает
    ### В случае паранои:
    - хранить список валидных IP, deviceID, fingerprint браузера, генерить рандомный randomUserID
    - дополнительно шифровать токены (в nodejs например crypt >> aes-256)
    - зашивать в payload также IP/подсеть владельца токена. В этом случае при каждой попытке зайти с новой точки доступа к интерету придется перелогиниватся.

    ### Пример имплементации:
    __Front-end:__ https://github.com/zmts/beauty-vuejs-boilerplate/blob/master/src/services/http.init.js
  13. @zmts zmts revised this gist Aug 20, 2018. 1 changed file with 0 additions and 5 deletions.
    5 changes: 0 additions & 5 deletions tokens.md
    Original file line number Diff line number Diff line change
    @@ -76,11 +76,6 @@ __Если рассматривать возможность аутентифи
    | 1 | alex | { refreshTokenId1: 'refreshTokenBody1', refreshTokenId2: 'refreshTokenBody2'}
    -------------------------------------------------------------------------------------------------
    ```
    При создании рефрештокена я дополнительно его шифрую. __П__ - паранойя.
    https://github.com/zmts/supra-api-nodejs/blob/master/services/auth/cryptoEncryptService.js
    И использую время создания (__timestamp__) в качестве __refreshTokenId__. Но можно прекрасно обойтись и без дополнительного шифрования и без JSONB, просто создав для рефреш токенов отдельную таблицу __one-to-many__. Возможно стоит так сделать и мне.

    __Схема(без учета доп. шифрования):__
    1. Клиент проверяет перед запросом не истекло ли время жизни __access token'на__
    2. Если истекло клиент отправляет на `auth/refresh-token` URL __refresh token__
    3. Сервер берет __user_id__ из __payload'a__ __refresh token'a__ по нему ищет в БД запись данного юзера и достает из него __refresh token__
  14. @zmts zmts revised this gist Aug 10, 2018. 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
    @@ -14,7 +14,7 @@ __Авторизация(authorization — разрешение, уполном
    1. Юзер c емайлом _**sasha_gmail.com**_ успешно прошел аутентификацию
    2. Сервер посмотрел в БД какая роль у юзера
    3. Сервер сгенерил юзеру токен с указанной ролью
    4. Юзер заходит на некий ресурс
    4. Юзер заходит на некий ресурс используя полученный токен
    5. Сервер смотрит на права(роль) юзера в токене и соотвественно пропускает или отсекает запрос

    Собственно п.5 и есть процесс __авторизации__.
  15. @zmts zmts revised this gist Jul 25, 2018. 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
    @@ -127,15 +127,16 @@ __Front-end:__ https://github.com/zmts/beauty-vuejs-boilerplate/blob/master/src/
    __Back-end:__ https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth

    ### Чтиво:
    - Заметка базируется на: https://habrahabr.ru/company/Voximplant/blog/323160/
    - https://tools.ietf.org/html/rfc6749
    - https://jwt.io/introduction/
    - https://auth0.com/blog/using-json-web-tokens-as-api-keys/
    - https://auth0.com/blog/cookies-vs-tokens-definitive-guide/
    - https://auth0.com/blog/ten-things-you-should-know-about-tokens-and-cookies/
    - https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
    - https://habrahabr.ru/company/dataart/blog/262817/
    - https://habr.com/post/340146/
    - https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
    - Заметка базируется на: https://habrahabr.ru/company/Voximplant/blog/323160/
    - 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
  16. @zmts zmts revised this gist Jul 15, 2018. 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
    @@ -58,7 +58,7 @@ __Поскольку токены это не зашифрованная инф
    4. Сравнивает __refresh token__ клиента с __refresh token'ом__ найденным в БД
    5. Проверяет валидность и срок действия __refresh token'а__
    6. В случае успеха сервер:
    1. Создает и записывает __refresh token__ в БД
    1. Создает и перезаписывает __refresh token__ в БД
    2. Создает новый __access token__
    3. Отправляет оба токена и новый `expires_in` __access token'а__ клиенту
    7. Клиент повторяет запрос к API c новым __access token'ом__
  17. @zmts zmts revised this gist Jul 10, 2018. 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
    @@ -138,5 +138,6 @@ __Back-end:__ https://github.com/zmts/supra-api-nodejs/tree/master/actions/auth
    - Заметка базируется на: https://habrahabr.ru/company/Voximplant/blog/323160/
    - 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


  18. @zmts zmts revised this gist May 31, 2018. 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
    @@ -1,4 +1,4 @@
    # Про токены, JSON Web Tokens (JWT), аутентификацию и авторизацию. Token-Based Authentication(JWT)
    # Про токены, JSON Web Tokens (JWT), аутентификацию и авторизацию. Token-Based Authentication

    ## Preconditions:
    В данной заметке рассматривается работа JWT с __симметичным__ алгоритмом шифрования (HS256/HS384/HS512)
  19. @zmts zmts revised this gist May 19, 2018. 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
    @@ -1,4 +1,4 @@
    # Token-Based Authentication(JWT)
    # Про токены, JSON Web Tokens (JWT), аутентификацию и авторизацию. Token-Based Authentication(JWT)

    ## Preconditions:
    В данной заметке рассматривается работа JWT с __симметичным__ алгоритмом шифрования (HS256/HS384/HS512)
  20. @zmts zmts revised this gist May 1, 2018. 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
    @@ -122,7 +122,7 @@ __Проблема:__ Поскольку __refresh token__ продлевает
    - дополнительно шифровать токены (в nodejs например crypt >> aes-256)

    ### Пример имплементации:
    __Front-end:__ https://github.com/zmts/vuejs-boilerplate/blob/master/src/services/http.init.js
    __Front-end:__ https://github.com/zmts/beauty-vuejs-boilerplate/blob/master/src/services/http.init.js

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

  21. @zmts zmts revised this gist May 1, 2018. 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
    @@ -78,7 +78,7 @@ __Если рассматривать возможность аутентифи
    ```
    При создании рефрештокена я дополнительно его шифрую. __П__ - паранойя.
    https://github.com/zmts/supra-api-nodejs/blob/master/services/auth/cryptoEncryptService.js
    И использую эго __Initialization Vector__ в качестве __refreshTokenId__. Но можно прекрасно обойтись и без дополнительного шифрования и без JSONB, просто создав для рефреш токенов отдельную таблицу __one-to-many__. Возможно стоит так сделать и мне.
    И использую время создания (__timestamp__) в качестве __refreshTokenId__. Но можно прекрасно обойтись и без дополнительного шифрования и без JSONB, просто создав для рефреш токенов отдельную таблицу __one-to-many__. Возможно стоит так сделать и мне.

    __Схема(без учета доп. шифрования):__
    1. Клиент проверяет перед запросом не истекло ли время жизни __access token'на__
  22. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -99,7 +99,7 @@ __Схема(без учета доп. шифрования):__

    Как дополнительная мера можно вообще заблокировать данного юзера при попытке залогинится более чем на 10ти устройствах. С возможностью разблокировки только через email. Но в этом случае нам необходимо будет во время каждого рефреша проверять список токенов на наличие мертвых(не валидных).

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

    Возникает вопрос зачем __refresh token'y__ срок жизни, если он обновляется каждый раз при обновлении __access token'a__ ? Это сделано на случай если юзер будет в офлайне более 60 дней, тогда прийдется заново вбить логин/пароль.
  23. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -99,7 +99,8 @@ __Схема(без учета доп. шифрования):__

    Как дополнительная мера можно вообще заблокировать данного юзера при попытке залогинится более чем на 10ти устройствах. С возможностью разблокировки только через email. Но в этом случае нам необходимо будет во время каждого рефреша проверять список токенов на наличие мертвых(не валидных).

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

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

  24. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -51,10 +51,6 @@ __Поскольку токены это не зашифрованная инф
    3. Клиент сохраняет токены и время смерти __access token'а__, используя __access token__ для последующей авторизации запросов
    4. Перед каждым запросом клиент предварительно проверяет время жизни __access token'а__ (из `expires_in`)и если оно истекло использует __refresh token__ чтобы обновить __ОБА__ токена и продолжает использовать новый __access token__

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

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

    ## Схема рефреша токенов (api/auth/refresh-tokens):
    1. Клиент проверяет перед запросом не истекло ли время жизни __access token'на__
    2. Если истекло клиент отправляет на `auth/refresh-token` URL __refresh token__
    @@ -103,6 +99,10 @@ __Схема(без учета доп. шифрования):__

    Как дополнительная мера можно вообще заблокировать данного юзера при попытке залогинится более чем на 10ти устройствах. С возможностью разблокировки только через email. Но в этом случае нам необходимо будет во время каждого рефреша проверять список токенов на наличие мертвых(не валидных).

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

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

    ## В случае кражи(обоих токенов):
    1. Хакер воспользовался __access token'ом__
    2. Закончилось время жизни __access token'на__
  25. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -40,7 +40,7 @@ __refresh token__ - выдается сервером по результам у

    __Поскольку токены это не зашифрованная информация крайне не рекомендуется хранить в них такую информацию как пароли__

    ## Схема создания/использования токенов:
    ## Схема создания/использования токенов (api/auth/login):
    1. Пользователь логинится в приложении, передавая логин/пароль на сервер
    2. Сервер проверят подлинность логина/пароля, в случае удачи генерирует и отправляет клиенту два токена(__access, refresh__) и время смерти __access token'а__ (`expires_in` поле, в __unix timestamp__). Также в __payload__ __refresh token'a__ добавляется __user_id__
    ```
    @@ -55,7 +55,7 @@ __Ключевой момент__ что в момент рефреша то е

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

    ## Схема рефреша токенов:
    ## Схема рефреша токенов (api/auth/refresh-tokens):
    1. Клиент проверяет перед запросом не истекло ли время жизни __access token'на__
    2. Если истекло клиент отправляет на `auth/refresh-token` URL __refresh token__
    3. Сервер берет __user_id__ из __payload'a__ __refresh token'a__ по нему ищет в БД запись данного юзера и достает из него __refresh token__
    @@ -71,7 +71,7 @@ __С такой схемой юзер сможет быть залогинен

    __Если рассматривать возможность аутентификации на более чем одном девайсе/браузере:__ необходимо хранить весь список валидных рефреш токенов юзера. Если юзер авторизовался более чем на ±10ти устройствах(что есть весьма подозрительно), автоматически инвалидоровать все рефреш токены кроме текущего и отправлять email с security уведомлением. Как вариант список токенов можно хранить в jsonb(если используется PostgreSQL).

    ## Схема рефреша токенов (мульти сессии):
    ## Схема рефреша токенов (мульти сессии, api/auth/refresh-tokens):
    Для использования возможности аутентификации на более чем одном девайсе необходимо хранить все рефреш токены по каждому юзеру. Я этот список храню в записи юзера в виде JSONB.
    ```
    -------------------------------------------------------------------------------------------------
  26. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -81,7 +81,7 @@ __Если рассматривать возможность аутентифи
    -------------------------------------------------------------------------------------------------
    ```
    При создании рефрештокена я дополнительно его шифрую. __П__ - паранойя.
    https://github.com/zmts/supra-api-nodejs/blob/master/services/auth/cryptoEncryptServiceSync.js
    https://github.com/zmts/supra-api-nodejs/blob/master/services/auth/cryptoEncryptService.js
    И использую эго __Initialization Vector__ в качестве __refreshTokenId__. Но можно прекрасно обойтись и без дополнительного шифрования и без JSONB, просто создав для рефреш токенов отдельную таблицу __one-to-many__. Возможно стоит так сделать и мне.

    __Схема(без учета доп. шифрования):__
  27. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -89,7 +89,7 @@ __Схема(без учета доп. шифрования):__
    2. Если истекло клиент отправляет на `auth/refresh-token` URL __refresh token__
    3. Сервер берет __user_id__ из __payload'a__ __refresh token'a__ по нему ищет в БД запись данного юзера и достает из него __refresh token__
    4. Сравнивает __refresh token__ клиента с __refresh token'ом__ найденным в БД
    5. Проверяет валидность и срок действия __refresh token'а__
    5. Проверяет валидность и срок действия __refresh token'а__ (но если токен не валиден удаляет его сразу)
    6. В случае успеха сервер:
    1. __Удаляет старый рефреш токен__
    2. Проверяет количество уже существующих решфреш токенов.
    @@ -99,7 +99,9 @@ __Схема(без учета доп. шифрования):__
    6. Отправляет оба токена и новый `expires_in` __access token'а__ клиенту
    7. Клиент повторяет запрос к API c новым __access token'ом__

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

    Как дополнительная мера можно вообще заблокировать данного юзера при попытке залогинится более чем на 10ти устройствах. С возможностью разблокировки только через email. Но в этом случае нам необходимо будет во время каждого рефреша проверять список токенов на наличие мертвых(не валидных).

    ## В случае кражи(обоих токенов):
    1. Хакер воспользовался __access token'ом__
  28. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -99,7 +99,7 @@ __Схема(без учета доп. шифрования):__
    6. Отправляет оба токена и новый `expires_in` __access token'а__ клиенту
    7. Клиент повторяет запрос к API c новым __access token'ом__

    Таким образом если юзер залогинился на пяти устройствах, рефреш токены будут постоянно обновлятся и все счастливы. Но если с аккаунтом юзера начнут производить подозрительные действия(попытатся залогинится более чем на 10ти устройствах) система сбросит все сессии(рефреш токены) кроме последней. В дополнение можно выслать юзеру на мыло уведомление.
    Таким образом если юзер залогинился на пяти устройствах, рефреш токены будут постоянно обновлятся и все счастливы. Но если с аккаунтом юзера начнут производить подозрительные действия(попытаются залогинится более чем на 10ти устройствах) система сбросит все сессии(рефреш токены) кроме последней. Либо как дополнительная мера, вообще заблокировать данного юзера. С возможностью разблокировки только через email.

    ## В случае кражи(обоих токенов):
    1. Хакер воспользовался __access token'ом__
  29. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -97,7 +97,7 @@ __Схема(без учета доп. шифрования):__
    4. Если их меньше 10 просто создает и записывает новый в БД.
    5. Создает новый __access token__
    6. Отправляет оба токена и новый `expires_in` __access token'а__ клиенту
    7. Клиент повторяет запрос к API c новым __access token'ом__.
    7. Клиент повторяет запрос к API c новым __access token'ом__

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

  30. @zmts zmts revised this gist Apr 15, 2018. 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
    @@ -97,7 +97,8 @@ __Схема(без учета доп. шифрования):__
    4. Если их меньше 10 просто создает и записывает новый в БД.
    5. Создает новый __access token__
    6. Отправляет оба токена и новый `expires_in` __access token'а__ клиенту
    7. Клиент повторяет запрос к API c новым __access token'ом__
    7. Клиент повторяет запрос к API c новым __access token'ом__.

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

    ## В случае кражи(обоих токенов):