# Token-Based Authentication(JWT) ## Preconditions: В данной заметке рассматривается работа JWT с __симметичным__ алгоритмом шифрования (HS256/HS384/HS512) ## Основы: __Аутентификация(authentication, от греч. αὐθεντικός [authentikos] – реальный, подлинный; от αὐθέντης [authentes] – автор)__ - это процесс проверки учётных данных пользователя (логин/пароль). Проверка подлинности пользователя путём сравнения введённого им пароля с паролем, сохранённым в базе данных пользователей; __Авторизация(authorization — разрешение, уполномочивание)__ - это проверка прав пользователя на доступ к определенным ресурсам. Например после аутентификации юзер _**sasha**_ получает право обращатся и получать от ресурса __"super.com/vip"__ некие данные. Во время обращения юзера _**sasha**_ к ресурсу __vip__ система авторизации проверит имеет ли право юзер обращатся к этому ресурсу (проще говоря переходить по неким разрешенным ссылкам) 1. Юзер c емайлом _**sasha_gmail.com**_ успешно прошел аутентификацию 2. Сервер посмотрел в БД какая роль у юзера 3. Сервер сгенерил юзеру токен с указанной ролью 4. Юзер заходит на некий ресурс 5. Сервер смотрит на права(роль) юзера в токене и соотвественно пропускает или отсекает запрос Собственно п.5 и есть процесс __авторизации__. *Дабы не путатся с понятиями __Authentication/Authorization__ можно использовать псевдонимы __checkPassword/checkAccess__(я так сделал в своей API)* __JSON Web Token (JWT)__ — содержит три блока, разделенных точками: заголовок(__header__), набор полей (__payload__) и __сигнатуру__. Первые два блока представлены в JSON-формате и дополнительно закодированы в формат base64. Набор полей содержит произвольные пары имя/значения, притом стандарт JWT определяет несколько зарезервированных имен (iss, aud, exp и другие). Сигнатура может генерироваться при помощи и симметричных алгоритмов шифрования, и асимметричных. Кроме того, существует отдельный стандарт, отписывающий формат зашифрованного 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 ``` __Токены__ предоставляют собой средство __авторизации__ для каждого запроса от клиента к серверу. Токены(и соотвественно сигнатура токена) генерируются на сервере основываясь на секретном ключе(который хранится на сервере) и __payload'e__. Токен в итоге хранится на клиенте и используется при необходимости __авторизации__ како-го либо запроса. Такое решение отлично подходит при разаработке SPA. При попытке хакером подменить данные в __header'ре__ или __payload'е__, токен cтанет не валидным, поскольку сигнатура не будет соответствовать изначальным значениям. А возможность сгенерировать новую сигнатуру у хакера отсутствует, поскольку секретный ключ для зашифровки лежит на сервере. __access token__ - используется для __авторизации запросов__ и хранения дополнительной информации о пользователе (аля __user_id__, __user_role__ или еще что либо, эту информацию также называет __payload__) __refresh token__ - выдается сервером по результам успешной аутентификации и используется для получения нового __access token'a__ и обновления __refresh token'a__ Каждый токен имеет свой срок жизни, например __access__: 30мин, __refresh__: 60дней __Поскольку токены это не зашифрованная информация крайне не рекомендуется хранить в них такую информацию как пароли__ ## Схема создания/использования токенов: 1. Пользователь логинится в приложении, передавая логин/пароль на сервер 2. Сервер проверят подлинность логина/пароля, в случае удачи генерирует и отправляет клиенту два токена(__access, refresh__) и время смерти __access token'а__ (`expires_in` поле, в __unix timestamp__). Также в __payload__ __refresh token'a__ добавляется __user_id__ ``` "accessToken": "...", "refreshToken": "...", "expires_in": 1502305985425 ``` 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 дней, тогда прийдется заново вбить логин/пароль. С такой схемой юзер сможет быть залогинен только на одном устройстве. Тоесть в любом случае при смене устройства ему придется логинится заново. ## Схема рефреша токенов: 1. Клиент проверяет перед запросом не истекло ли время жизни __access token'на__ 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'а__ 6. В случае успеха сервер: 1. Пересоздает и записывает __refresh token__ в БД 2. Создает новый __access token__ 3. Отправляет оба токена и новый `expires_in` __access token'а__ клиенту 7. Клиент повторяет запрос к API c новым __access token'ом__ ## В случае кражи(обоих токенов): 1. Хакер воспользовался __access token'ом__ 2. Закончилось время жизни __access token'на__ 3. __Клиент хакера__ отправляет __refresh token__ 4. Хакер получает новую пару токенов 5. На сервере создается новая пара токенов(__"от хакера"__) 5. Юзер пробует зайти на сервер >> обнаруживается что токены невалидны 6. Сервер перенаправляет юзера на форму аутентификации 7. Юзер вводит логин/пароль 8. Создается новая пара токенов >> пара токенов __"от хакера"__ становится не валидна __Проблема:__ Поскольку __refresh token__ продлевает срок своей жизни каждый раз при рефреше токенов >> хакер пользуется токенами до тех пор пока юзер не залогинится. ### В случае паранои: - хранить список валидных IP, deviceID, fingerprint браузера, генерить рандомный randomUserID - дополнительно шифровать токены (в nodejs например crypt >> aes-256) ### Чтиво: - 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://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