Краткое руководство по Auth0 Python — проверка подписи и срока действия токена идентификатора

Я создаю приложение Python Flask с Auth0 в качестве поставщика OpenID Connect. Веб-сайт Auth0 предоставляет некоторый скелетный код , который я использую в качестве отправной точки для своего приложения. Скелетный код работает и легко расширяется; однако есть некоторые неясности относительно того, что делает код и почему его поведение соответствует современным стандартам безопасности. Я не могу позволить себе быть менее чем на 100 % уверенным, когда речь идет о безопасности, поэтому я хотел бы обсудить эти неясности с экспертами здесь, в StackOverflow.

Мое понимание скелетного кода

Ниже приведена последовательность событий, происходящих, когда пользователь взаимодействует с этим каркасным приложением. (См. код ниже.)

  1. Пользователь открывает http://localhost:3000/ в браузере. Это вызывает конечную точку home в приложении Flask. На данный момент у пользователя нет файла cookie сеанса, поэтому ответ представляет собой некоторый HTML-код, содержащий слова «Привет, гость» и кнопку входа.
  2. Пользователь нажимает кнопку входа, которая является ссылкой на http://localhost:3000/login. Это вызывает конечную точку login в приложении Flask, которая перенаправляет пользователя в окно входа Auth0.
  3. Пользователь вводит свой адрес электронной почты и имя пользователя в поле входа Auth0. Пользователь перенаправляется на http://localhost:3000/callback; код авторизации, созданный при успешном входе в систему, передается в строке запроса в этом URL-адресе.
  4. Вызывается конечная точка callback в приложении Flask. Код авторизации отправляется на Auth0 в обмен на токен ID и токен доступа. Устанавливается файл cookie сеанса, содержащий этот токен идентификатора и токен доступа. Пользователь перенаправляется на http://localhost:3000/.
  5. Конечная точка home вызывается снова. На этот раз у пользователя есть файл cookie сеанса. Конечная точка возвращает некоторый HTML-код, содержащий текст Welcome {username}, а также дополнительную информацию о пользователе, содержащуюся в токене идентификатора в файле cookie сеанса.

И как именно создается файл cookie сеанса? Насколько я понимаю, файл cookie сеанса содержит токен идентификатора и токен доступа, а также подпись. Подпись создается с использованием секретного ключа приложения Flask (см. строку 8 в примере кода ниже).


Вопрос 1: Проверка подписи JWT

Токен ID — это JWT. Будучи JWT, токен ID содержит подпись. Эта подпись подписывается с использованием закрытого ключа Auth0 и может быть проверена с помощью открытого ключа Auth0 (также известного как JWK).

Поскольку Auth0 пытается подписать токен идентификатора, я ожидаю, что любая конечная точка в нашем приложении Flask, которая использует токен идентификатора в качестве доказательства личности пользователя, должна проверять подпись на токене идентификатора с помощью открытого ключа Auth0. В противном случае, какой смысл в Auth0 подписывать токен ID?

Но конечная точка home использует токен идентификатора в качестве доказательства личности пользователя и не проверяет подпись на токене идентификатора! (По крайней мере, такое впечатление у меня сложилось, когда я читал код и щелкал с нажатой клавишей Ctrl через методы библиотеки. Тем не менее, я не слишком уверен в этом утверждении, поскольку библиотека authlib.integrations.flask_client не очень дружелюбна к нажатию Ctrl.)

Мои вопросы:

  • Правильно ли я понимаю, что подпись внутри токена ID не проверяется конечной точкой home?
  • Если я прав, то это проблема? Должен ли я это исправить?

Предупреждение: в этой настройке есть две подписи:

  • Подпись внутри токена ID, который подписан закрытым ключом Auth0 и может быть проверен с помощью открытого ключа Auth0.
  • Подпись файла cookie сеанса, который подписывается с использованием секретного ключа приложения Flask.

Злоумышленник не может подделать файл cookie сеанса, поскольку у злоумышленника нет секретного ключа приложения Flask, и поэтому он не может создать действительную подпись для файла cookie сеанса. Так что, на мой наивный взгляд, приложение кажется безопасным. Конечно, приложению не удается проверить подпись в токене идентификатора, но оно компенсирует это проверкой подписи в файле cookie сеанса.

Тем не менее, я подозреваю, что подпись в токене идентификатора должна быть там по уважительной причине, по-видимому, для защиты от какой-то атаки, о которой мой неопытный мозг не подумал. Вполне вероятно, что я что-то упускаю.

Вопрос 2: Проверка срока действия JWT

Маркер идентификатора содержит срок действия. Насколько я могу судить, конечная точка home не проверяет, не истек ли срок действия токенов ID.

Опять же, я не уверен на 100%, прав ли я в этом вопросе.

Мои вопросы:

  • Действительно ли конечная точка home не проверяет срок действия токена ID?
  • Если да, то является ли это ошибкой?

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


Примеры кода:

сервер.py

import json
from os import environ as env

from authlib.integrations.flask_client import OAuth
from flask import Flask, redirect, render_template, session, url_for
    
app = Flask(__name__)
app.secret_key = env.get("APP_SECRET_KEY")

oauth = OAuth(app)

oauth.register(
    "auth0",
    client_id=env.get("AUTH0_CLIENT_ID"),
    client_secret=env.get("AUTH0_CLIENT_SECRET"),
    client_kwargs = {"scope": "openid profile email"},
    server_metadata_url=f'https://{env.get("AUTH0_DOMAIN")}/.well-known/openid-configuration'
)

@app.route("/login")
def login():
    return oauth.auth0.authorize_redirect(
        redirect_uri=url_for("callback", _external=True)
    )

@app.route("/callback", methods=["GET", "POST"])
def callback():
    token = oauth.auth0.authorize_access_token()
    session["user"] = token
    return redirect("/")

@app.route("/")
def home():
    return render_template(
      "home.html",
      session=session.get('user'),
      pretty=json.dumps(session.get('user'), indent=4)
    )

if __name__ == "__main__":
    app.run(host = "0.0.0.0", port=env.get("PORT", 3000))

шаблоны/home.html

<html>
<head>
  <meta charset = "utf-8" />
  <title>Auth0 Example</title>
</head>
<body>
  {% if session %}
      <h1>Welcome {{session.userinfo.name}}!</h1>
      <p><a href = "/logout">Logout</a></p>
      <div><pre>{{pretty}}</pre></div>
  {% else %}
    <h1>Welcome Guest</h1>
    <p><a href = "/login">Login</a></p>
  {% endif %}
</body>
</html>
Новые приложения с использованием ChatGPT
Новые приложения с использованием ChatGPT
Я собираюсь вернуться к теме, которую уже освещал ранее, - чатгпт.
Развертывание модели машинного обучения с помощью Flask - Angular в Kubernetes
Развертывание модели машинного обучения с помощью Flask - Angular в Kubernetes
Kubernetes - это портативная, расширяемая платформа с открытым исходным кодом для управления контейнерными рабочими нагрузками и сервисами, которая...
Другой маршрут в Flask Python
Другой маршрут в Flask Python
Flask - это фреймворк, который поддерживает веб-приложения. В этой статье я покажу, как мы можем использовать @app .route в flask, чтобы иметь другую...
1
0
196
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Сеансы Flask по умолчанию привязаны к браузеру, если вы не сделаете сеанс постоянным.

Проверьте браузер, просмотрите страницу браузера и найдите файлы cookie/localstorage, которые устанавливает фляга.

Спасибо. Я согласен с тем, что Flask устанавливает файлы cookie в браузере. Не могли бы вы помочь мне понять, какое значение это имеет для вопросов, которые я задал?

Kenny Wong 23.04.2023 22:10

/home только проверяет, существует ли сессия.. если вы закрыли браузер и открыли снова.. сессия не будет существовать. Сессия не привязана к тайм-ауту/эксперименту auth0.

alphaadidas 24.04.2023 09:35

Я понимаю, что мне не хватало базовых знаний о файлах cookie. Файлы cookie могут либо иметь срок действия/максимальный возраст, либо они являются «сессионными» файлами cookie, что означает, что они удаляются при закрытии браузера. Файлы cookie, которые устанавливает Flask, являются файлами cookie сеанса. Однако мой вопрос заключается в том, является ли правильным поведением (в соответствии со стандартами OIDC) для Flask проверка подписи и срока действия токена идентификатора JWT при каждом запросе, и если да, то реализуется ли это поведение скелетным кодом с сайта Auth0.

Kenny Wong 24.04.2023 23:46

правильно.. когда вы устанавливаете токен session["user"] = token, в идеале вы синхронизируете истечение сеанса с истечением срока действия токена. Чего в примере не происходит.

alphaadidas 25.04.2023 07:20

Так является ли соглашением OIDC устанавливать время истечения сеанса равным времени истечения срока действия токена идентификатора? Вы можете привести авторитетную ссылку?

Kenny Wong 25.04.2023 08:30

Кроме того, как насчет проверки подписи на JWT? Является ли соглашением OIDC, что сервер должен проверять подпись на JWT при каждом вызове /home?

Kenny Wong 25.04.2023 08:32

Спецификация OIDC: openid.net/specs/…

alphaadidas 26.04.2023 04:29
Ответ принят как подходящий

КОД ПОТОК

Современная форма потока кода авторизации OAuth 2.0 и OpenID Connect начинается с параметров этой формы, отправляемых в перенаправлении браузера (фронтальный канал):

GET https://login.example.com/oauth/v2/authorize
    ?client_id=my-client
    &redirect_uri=https://www.example.com/callback
    &response_type=code
    &scope=openid profile
    &code_challenge=WhmRaP18B9z2zkYcIlb4uVcZzjLqcZsaBQJf5akUxsA
    &code_challenge_method=S256
    &state=CfDJ8Nxa-YhPzjpBilDQz2C...

В браузер возвращается код авторизации, после чего идет запрос обратного канала на получение токенов. Обычно это направляется через серверную часть, где можно безопасно предоставить секрет клиента.

POST https://login.example.com/oauth/v2/token
Content-Type: application/x-www-form-urlencoded

client_id=my-client
&client_secret=U2U9EnSKx31fUnvgGR3coOUszko5MiuCSI2Z_4ogjIiO5-UbBzIBWU6JQQaljEis
&code=I9xL9DY9jAYHPuHSiW2OpWUaNRW4otei
&grant_type=authorization_code
&redirect_uri=https://www.example.com/callback
&code_verifier=HlfffYlGy7SIX3pYHOMJfhnO5AhUW1eOIKfjR42ue28

В веб-приложении наилучшей практикой является выпуск одного или нескольких безопасных файлов cookie только для HTTP, которые либо содержат токены, возвращенные из второго запроса, либо ссылаются на них. Как это сделать, не описано в документах по стандартам, но существуют общеизвестные передовые методы.

ПРОВЕРКА ИДЕНТИФИКАЦИОННОГО ТОКЕНА

Идентификационный токен является доказательством события аутентификации, и срок его действия обычно не учитывается. Когда все токены получены по обратному каналу через доверенный URL-адрес HTTPS, подпись токена ID не обязательно проверять, хотя имеет смысл проверить ожидаемого эмитента и аудиторию. Это нужно сделать только один раз, после чего выдаются файлы cookie уровня приложения.

СОЗДАНИЕ СЕАНСОВОГО КУКИ

Обычно веб-приложение использует библиотеку для создания зашифрованных файлов cookie сеанса с надежным алгоритмом шифрования, например AES256-GCM. Ключ шифрования известен только серверу. Один из вариантов — хранить токен идентификатора, токен обновления и токен доступа в отдельных файлах cookie, чтобы удерживать их таким образом, чтобы бэкенду было легко управлять и который лучше всего соответствовал ограничениям браузера и HTTP-сервера. Это также гарантирует, что время жизни файла cookie будет привязано к времени жизни токена.

ИСКЛЮЧЕНИЕ СЕССИИ

При каждом запросе данных файлы cookie сеанса проверяются и расшифровываются. Также следует применять защиту от файлов cookie от OWASP. Базовые токены доступа затем используются для запроса данных, например, из API.

Когда срок действия маркеров доступа истекает, маркеры обновления используются для обновления маркеров доступа, а файлы cookie перезаписываются, чтобы содержать обновленные маркеры. Обычно срок действия сеанса соответствует сроку действия токена обновления. Когда токен доступа не может быть обновлен, пользователь перенаправляется на страницу с истекшим сроком действия сеанса и предлагает снова войти в систему, что запускает новый поток кода.

КРАТКОЕ СОДЕРЖАНИЕ

Защищенные OAuth веб-приложения состоят из нескольких движущихся частей, поскольку безопасность браузера — непростая тема. Существуют как файлы cookie в качестве веб-учетных данных, так и токены в качестве учетных данных данных. Использование хорошей библиотеки Python OpenID Connect поможет с деталями.

Я доволен этим. Поскольку веб-сервер получает токен идентификатора непосредственно от поставщика удостоверений, нет особого смысла проверять подпись на токене идентификатора. Я не понимал, что стандартная практика заключается в том, чтобы истечение срока действия сеанса было привязано к истечению срока действия токена обновления, но это имеет смысл — после истечения срока действия токена обновления пользователю необходимо пройти через другой поток входа в систему, чтобы взаимодействовать с приложением. Хорошая идея — использовать разные файлы cookie для разных токенов, срок действия которых истекает в разное время. Я согласен с тем, что файлы cookie должны быть HttpOnly и Secure (и, возможно, SameSite Strict).

Kenny Wong 25.04.2023 21:27

Кстати, пример кода Auth0 Flask не работает так же, как идеальные лучшие практики, которые вы описали. Во-первых, пример кода Auth0 Flask вообще не генерирует токены обновления, поэтому некоторые из ваших советов по передовой практике неприменимы. Файл cookie сеанса содержит как токен идентификатора, так и токен доступа, объединенные вместе, и этот файл cookie не имеет установленного срока действия (поэтому он удаляется, когда пользователь закрывает браузер). Во всяком случае, теперь у меня есть полное понимание того, что делает пример кода Auth0, а также полное понимание того, каковы лучшие практики.

Kenny Wong 25.04.2023 22:12

Другие вопросы по теме