Authz Practice # Задача Необходимо реализовать схему OAuth2 Authorization Code Grant with PKCE. Под реализацией подразумевается описание следующих частей системы: 1. REST API, для каждой точки доступа: * список входных параметров и требований к ним (обязательность, формат и т.п.). * варианты ответа сервера. * пошаговый алгоритм обработки запроса. 2. форматов используемых токенов, например, параметров JWT токена и полей в его JSON части. 3. записи в базах данных, создаваемые/обновляемые в алгоритмах из п.1: * формат записей. * оценка времени жизни (момента в будущем, когда запись можно безопасно удалять). * оценка количества записей в зависимости от количества пользователей и максимального количества сессий у каждого. # Требования 1. Минимизировать количество данных, которые необходимо хранить на стороне сервера авторизации для корректного функционирования системы. 1. Добавить возможность пользователю инвалидировать все токены, полученные в ходе выполнения потока авторизации. 1. Гарантировать, что Refresh Token может быть использован только один раз. # Решение Таблицы application_clients - client_id - redirect_uri auth_codes - auth_code (unique) - application_client_id - valid_till refresh_token - user_id - token (unique) - valid_till users - id - application_client_id - secret - session_invalidated_at 1. Ендпоинт для получения кода доступа domain.com/api/v1/auth?response_type=code&client_id=CLIENT_ID domain.com/path - API authorization endpoint response_type [REQUIRED] - code запрашиваем доступ с помощью кода авторизации. client_id [REQUIRED] - идентификатор приложения Валидация * response_type -- REQUIRED -- value = code * client_id -- REQUIRED -- есть в таблице application_clients При получении запроса 1. Валидация полученых параметров 2. Если запрос невалидный - ответ с ошибкой 3.1. Проверка того, что пользователь выдал права 3.2. Cоздание записи в таблице auth_codes c valid_till=now+1min 3.2. Создаем запись в таблице users response В случае успешного получения прав HTTP/1.1 302 Found Location: REDIRECT_URL?code=qwerty В случае неуспешного получения прав HTTP/1.1 302 Found Location: REDIRECT_URL?error=access_denied 2. Ендпоинт для получения токена POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=qwerty&client_id=client_id Валидация * grant_type -- REQUIRED -- value = authorization_code * code -- REQUIRED -- value = authorization_code из таблицы auth_codes с valid_till < now() and client_id = request.client_id * client_id -- REQUIRED -- есть в таблице application_clients При получении запроса 1. Валидация полученых параметров 2.1. Если данные невалидны - ответ с ошибкой 2.2.1. Если данные валидны 2.2.2. Удаляем запись из таблицы auth_codes 2.2.3. Создаем запись в таблице refresh_tokens 2.2.5. Генерируем jwt token c использованием users.secret [Структура-токена](https://hackmd.io/uud4W9PATJKwchbHp19lYw?both#Структура-токена) response В случае неуспешного запроса HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"access_token", "token_type":"bearer" "expires_in":3600, "refresh_token":"refresh_token" } В случае успешного запроса HTTP/1.1 400 Bad Request Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "error":"invalid_request" } Возможные error - invalid_request - unauthorized_client - access_denied - unsupported_response_type - server_error - temporarily_unavailable 3. Ендпоинт для инвалидации токена POST /invalidate-token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded refresh_token=refresh_token&access_token=access_token&client_id=client_id * access_token -- REQUIRED -- value = access_token * refresh_token -- REQUIRED -- value = refresh_token * client_id -- REQUIRED -- есть в таблице application_clients * access_token validation При получении запроса 1. Валидация полученых параметров 2.1. Если данные невалидны - ответ с ошибкой 2.2.1. Если данные валидны 2.2.2. access_token и refresh_token валидны 2.2.3. По user_id (полученому из токена) удаляем записи в таблице refresh_tokens и users.tokens_invalidated_at=now() 4. Ендпоинт для получения token по refresh_token POST /token-by-refresh-token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded refresh_token=refresh_token&client_id=client_id Валидация * refresh_token -- REQUIRED -- value = refresh_token из таблицы refresh_tokens client_id = request.client_id and user_id = request.user_id * client_id -- REQUIRED -- есть в таблице application_clients При получении запроса 1. Валидация полученых параметров 2.1. Если данные невалидны - ответ с ошибкой 2.2.1. Если данные валидны 2.2.2. refresh_token валидный 2.2.3. Удаляем найденную запись из refresh_tokens 2.2.4. Создаем запись в таблице refresh_tokens response В случае успешного запроса HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"access_token", "token_type":"bearer" "expires_in":3600, "refresh_token":"refresh_token" } В случае неуспешного запроса HTTP/1.1 400 Bad Request Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "error":"invalid_request" } ### Структура токена JWT состоит из трех частей: заголовок header, полезные данные payload и подпись signature. header = { "alg": "HS256", "typ": "JWT"} payload = { "userId": "userId", "exp": unixExpTime, "createdAt": "createdAt" } ``` secretKey = users.secret unsignedToken = base64urlEncode(header) + '.' + base64urlEncode(payload) signature = HMAC-SHA256(unsignedToken, secretKey) ``` валидация токена - проверка signature - проверка unixExpTime (токен активный) - проверка что userId есть в таблице users - проверка что createdAt > users.session_ivalidated_at хранение токенов - access_token храним в Cookie - refresh_token храним в Local storage ### Удаление данных Можна удалять - Из таблицы auth_codes данные можно удалять когда valid_till > now(). В этой таблице хранятся только актуальные коды - из таблицы refresh_tokens удаляются после первого использования ### Данные в таблицах application_clients - сколько приложений столько и записей auth_codes - если все юзера получают auth code в один момент, то количество записей равно количеству юзеров. Но после использования или протухания auth_code он удаляется из базы. refresh_token - количество юзеров минус невалидные токены users - количество юзеров