Как выглядит процесс авторизации через OAuth в Command-line interface приложении? В стандартном сценарии провайдер перенаправляет обратно на сайт или в мобильное приложение (в случае с OAuth 2), а как перенаправлять в программу в терминале?
В статье будет рассмотрен процесс OAuth авторизации в CLI приложении на примере HeadHunter.
Исходный код можно найти здесь.
Что такое OAuth?
OAuth 2 — это стандарт авторизации, который дает возможность приложениям, таким как Facebook, GitHub и DigitalOcean, запросить доступ к пользовательским аккаунтам через HTTP. Методика заключается в перенаправлении процесса проверки подлинности на сервис, где зарегистрирован аккаунт пользователя, и предоставлении разрешения приложениям третьих сторон на использование этого аккаунта. OAuth 2 предусматривает механизмы авторизации специально для веб и настольных приложений, а также для устройств мобильных платформ.
Абстрактный процесс авторизации

-
Приложение запрашивает у пользователя авторизацию на доступ к ресурсам сервиса.
-
Если пользователь авторизовал запрос, приложение получает разрешение на авторизацию.
-
Приложение запрашивает токен доступа у сервера авторизации (API), предоставляя аутентификацию своей собственной личности и разрешение на авторизацию.
-
Если личность приложения аутентифицирована и разрешение на авторизацию действительно, сервер авторизации (API) выдает токен доступа к приложению. Авторизация завершена.
-
Приложение запрашивает ресурс у сервера ресурсов (API) и предоставляет токен доступа для аутентификации.
-
Если токен доступа действителен, сервер ресурсов (API) передает ресурс приложению.
Авторизация в CLI

-
Инициирование авторизационного процесса пользователем.
-
CLI запускает локально временный сервер.
-
Переадресация пользователя командной строкой на веб-страницу авторизации с указанием идентификатора приложения и запросом на редирект обратно на локальный адрес после успешной авторизации на заданный порт.
-
Введение пользователем своих учетных данных на сайте провайдера.
-
После успешного входа, сервис авторизации пересылает пользователя обратно на локальный сервер с кодом доступа.
-
Локальный сервер регистрирует полученный запрос, передает код авторизации в CLI приложение и завершает свою работу.
-
Приложение в командной строке использует полученный код для запроса токена доступа у авторизационного сервиса.
-
Приложение командной строки получает возможность доступа к данным пользователя и может выполнять действия от его имени.
Регистрация приложения в HeadHunter
На странице https://dev.hh.ru/admin регистрируем новое приложение, в поле Redirect URI вводим https://localhost:1505/oauth/hh и ждем одобрения HH. После одобрения заявки будут доступны Client Id, Client Secret.
Создание приложения
В качестве CLI обертки был выбран Click, Flask будет выступать в роли временного веб-сервера.
Пользователь запускает CLI приложение, хочет авторизоваться. Для этого запускается подпроцесс для веб-сервера. Создается объект Queue и передается в дочерний процесс, в который сервер положит код авторизации.
from multiprocessing import Process, Queue HH_REDIRECT_HOST = "localhost" HH_REDIRECT_PORT = 1505 HH_REDIRECT_URI = f"https://{HH_REDIRECT_HOST}:{HH_REDIRECT_PORT}/oauth/hh" def run_server(queue): flask_app.config['queue'] = queue flask_app.run(HH_REDIRECT_HOST, HH_REDIRECT_PORT, ssl_context=('cert/cert.pem', 'cert/key.pem'), threaded=False, use_reloader=False) def authorize(client_id: str) -> str: queue = Queue() server = Process(target=run_server, args=(queue,)) click.launch(get_oauth_link(client_id)) server.start() server.join() authorization_code = queue.get() if authorization_code is None: raise RuntimeError("No authorization code") click.echo(click.style("Authorization code was gotten", fg="green")) return authorization_code
Пользователь перенаправляется на страницу с предложением ввести логин, пароль на сайте HH:
def get_oauth_link(client_id: str) -> str: url = "https://hh.ru/oauth/authorize?" params = {"client_id": client_id, "redirect_uri": HH_REDIRECT_URI, "response_type": "code"} click.echo(url + urllib.parse.urlencode(params)) return url + urllib.parse.urlencode(params)
В случает правильного логина и пароля HH перенаправляет пользователя на сайт с похожей ссылкой https://localhost:1505/oauth/hh?code=NU0OIMNIBUYINMPOKM8865LOINJI66U36BNKBP3F55V2UPJBUBL43553V2S9DJ
Веб-сервер, запущенный ранее, видит запрос, читает параметр code, передает его в межпроцессный буфер Queue и отключается.
flask_app = Flask(__name__) log = logging.getLogger('werkzeug') log.setLevel(logging.ERROR) @flask_app.route('/oauth/<provider>', methods=['GET']) def oauth(provider): match provider: case 'hh': code = request.args.get('code', default=None, type=str) case _: return jsonify({"status": "ERROR", "message": "Wrong provider"}) if code is None: return jsonify({"status": "ERROR", "message": "No code in redirect URI params"}) flask_app.config['queue'].put(code) return jsonify({"status": "SUCCESSFUL"}) # никогда не вернется @flask_app.teardown_request def shutdown_process(response): os.kill(os.getpid(), signal.SIGINT)
CLI приложение в это время ждет, когда веб-сервер отключится. После отключения получает код авторизации и делает запрос на получение кода доступа к данным (по документации это должен делать удаленный сервер, передача Client Secret на пользователькое устройство не предусмотрено, но для упрощения будет создан толстый клиент без бэкэнда).
def json_api_factory(response_model: type): def json_api(func): def json_api_wrapper(*args, **kwargs): res = func(*args, **kwargs) if res.status_code == 200: return response_model(**res.json()) else: click.echo(click.style(f"Request failed {res.status_code}: {res.json()}", fg='red')) return json_api_wrapper return json_api class OAuthTokenResponse(BaseModel): access_token: str token_type: str refresh_token: str expires_in: int class OAuth: def __init__(self, client_id: str, client_secret: str, redirect_uri: str): self.client_id = client_id self.client_secret = client_secret self.redirect_uri = redirect_uri @json_api_factory(OAuthTokenResponse) def token(self, authorization_code: str) -> OAuthTokenResponse: url = "https://hh.ru/oauth/token" body = { "grant_type": "authorization_code", "client_id": self.client_id, "client_secret": self.client_secret, "redirect_uri": self.redirect_uri, "code": authorization_code } headers = { "Content-Type": "application/x-www-form-urlencoded" } res = requests.post(url, data=body, headers=headers) return res
Полученный access_token добавляется в заголовки запросов:
Authorization: Bearer ACCESS_TOKEN
Вывод
OAuth необходим как безопасный метод авторизации, дающий сторонним приложениям ограниченный доступ к пользовательским данным, не раскрывая при этом логин и пароль от основного аккаунта. Он удобен тем, что позволяет пользователям легко и безопасно использовать различные сервисы и приложения, избегая необходимости создавать и запоминать новые учетные данные для каждого из них.
ссылка на оригинал статьи https://habr.com/ru/articles/786320/
Добавить комментарий