Всем по два пива, ведь теперь в Клубе есть свой OpenID провайдер!
Он позволит вашим ботам, сайтам и приложениям авторизовать пользователей и проверять, что они пришли из Клуба. Я так делаю у себя в блоге, но создатели сторонних ботов для чатиков и спецпроектов типа Сикрет Сант давно просили тоже дать им такую возможность.
Теперь она есть. Самые прошаренные и нетерпеливые могут уже пойти в настройки своего профиля, зарегать там ключик и получить все необходимые токены и OAuth-эндпоинты: https://vas3k.club/apps/
Для нормальных же людей, которые не фигачат OAuth-авторизацию каждый день, я напишу здесь пошаговый гайд как и что делать, чтобы вам тоже было красиво.
Примеры будут на Python, но если вы красавчик в других языках программирования — продублируйте ваш вариант в комментах, я закреплю. Другим будет полезно.
Шаг 0. Разобраться с OAuth и OpenID Connect
У всего этого великолепия довольно большой порог входа, даже если использовать уже готовые либы (как мы и будем). Если для вас это первый раз в жизни — придётся немного сесть и во всём разобраться.
Зато потом можете сразу добавлять скилл «IAM» в резюме. Рекомендую!
Для погружения с нуля рекомендую вот этот гайд: An Illustrated Guide to OAuth and OpenID Connect (перевод на русский).
Там в картинках рассказано как выглядит весь этот обмен токенами, кодами, и зачем всё это нужно.
TL;DR: OpenID Connect = OAuth 2 + JWT-токены. Раньше это был зоопарк разных протоколов, но сейчас всё более-менее пришло к одному стандарту. Если вы знакомы с OAuth, то OpenID это просто OAuth с унифицированным форматом токенов.
Шаг 1. Зарегистрировать своё «приложение» в Клубе
Чтобы Клуб мог выдавать вам токены, он должен знать что у вас за приложение. Это может быть бот, сайт, скрипт, да вообще что угодно. Создать его можно здесь: https://vas3k.club/apps/
В ответ вам дадут client_id
и client_secret
. Первый можно рассказывать всем. Второй нужно хранить в секретном месте на вашем бекенде, иначе любой сможет украсть вашу авторизацию и притвориться вами.
Однако, если вашему приложению не нужен доступ к аккаунтам юзеров, то можно вообще забить на OpenID и использовать service_token
— это такой универсальный токен, с которым вы сразу можете делать обычные запросы к API, как будто вы залогинены. Этот способ подходит многим простым ботам — через него работает Вастрик.Пей и Вастрик.Контроль, например.
Но в этом гайде мы рассматриваем именно аутентификацию живых юзеров на сторонних ресурсах, так что тут придётся поколдовать.
Шаг 2. Пишем код
Для проектов на Python я рекомендую использовать библиотеку Authlib. Она современная, даёт биндинги под Django и Flask, ну и сам Клуб внутри использует именно её. Вроде как Authlib даже умеет в асинхронность.
Вот их гайд по написанию OAuth-клиентов.
Там много примеров и деталей, так что здесь я покажу лишь самый минимум, а точнее как сделать логин через Клуб за 10 строчек кода.
Для начала создадим саму кнопку «Войти через Вастрик.Клуб».
<a href="/login/club">Войти через Вастрик.Клуб</a>
В это же время на бекенде мы инициализируем наш OAuth-клиент. Вам надо лишь подставить свой client_id
и client_secret
, всё остальное Authlib возьмет из .well-known конфига (который мы указали в server_metadata_url
), включая все эндпоинты и JWT-ключи.
Удобно. Спасибо десятилетиям полировки стандарта OpenID. Раньше было больнее.
from authlib.integrations.django_client import OAuth
oauth = OAuth()
oauth.register({
name="club",
client_id="YOUR_CLIENT_ID",
client_secret=os.getenv("GET_YOUR_CLIENT_SECRET_HERE"),
server_metadata_url="https://vas3k.club/.well-known/openid-configuration",
client_kwargs={"scope": "openid"},
})
Затем напишем простейший обработчик для нашей кнопки. Он по сути просто редиректит юзера на сайт Клуба с правильными GET-параметрами.
# GET /login/club
def login_club(request):
redirect_uri = "https://your.website/login/club/callback"
return oauth.club.authorize_redirect(request, redirect_uri)
Теперь у нас есть кнопка, которую юзер может кликнуть, и ему откроется классическая форма авторизации.
По нажатию "Разрешить" Клуб редиректнет юзера обратно на ваш сайт, но уже с неким новым волшебным временным кодом (который живет 5 минут). Он будет использовать тот redirect_uri
, который вы указали в запросе. В нашем случае это /login/club/callback
.
Код — это еще не токен, но уже почти. Если Клуб выдал вам код, значит он признал вас. Теперь вам надо просто обменять ваш временный код на настоящий access_token
.
Напишем обработчик для этого.
Мы делаем всё с бекенда, потому что так безопаснее и токен не перехватят злые люди посередине (и так рекомендуется стандартом). Но если у вас чисто клиентское приложение, то на свой страх и риск можете делать это и с фронта.
# GET /login/club/callback
def login_club_callback(request):
try:
token = oauth.club.authorize_access_token(request)
except OAuthError as ex:
# тут обрабатываем всякие ошибки авторизации
return render(request, "error.html", {...})
# парсим базовую инфу о пользователе из id_token
# в нём есть юзернейм и почта
user_slug = token["userinfo"]["sub"]
user_email = token["userinfo"]["email"]
# этих данных может быть достаточно для создания профиля
user, _ = User.objects.get_or_create(...)
# логиним юзера у себя
# стандартными средствами джанги
login(request, user)
# красава!
return reverse("profile")
Пример работающего кода на продакшене можно посмотреть в гитхабе моего блога.
Если вы используете другую либу или вообще хотите реализовать весь флоу руками, то гляньте на наш OpenID Configuration — есть вся необходимая инфа о том, какие эндпоинты выдают токены и ключи. Этот формат по идее тоже стандартен и должен автоматически жраться и другими фреймворками.
Шаг 3. Делаем запросы к API
Выданный вам access_token
живёт уже 24 часа, но его можно рефрешить через refresh_token
, если вам по каким-то причинам надо обращаться к API Клуба позднее. Так что не забудьте сохранить всё в базу, чтобы не просрать.
Хотя для простой аутентификации (как у меня в блоге) можно сразу сделать все нужные запросы и просто выбросить токен. Так даже безопаснее.
Например, вот так я получаю данные профиля пользователя, которого только что аутентифицировал. Всё делается через простой эндпоинт: https://vas3k.club/user/me.json
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" https://vas3k.club/user/me.json
Ну или то же самое на питоне.
import requests
# запрашиваем профиль с аватаркой итд
club_profile = requests.get(
url="https://vas3k.club/user/me.json",
headers={
"Authorization": f"{token['token_type']} {token['access_token']}"
}
).json()
# например, вы хотите отфильтровать
# только членов Клуба с активной подпиской
if club_profile["user"]["payment_status"] != "active":
return render(
request, "error.html",
{"message": "Ваша подписка истекла"}
)
# заводим юзера у себя
user, _ = User.objects.update_or_create(
club_slug=user_slug,
defaults=dict(
full_name=club_profile["user"]["full_name"],
avatar=club_profile["user"]["avatar"],
email=user_email,
)
)
👍 Вы великолепны
Остальные API-эндпоинты можно подсмотреть на странице https://vas3k.club/apps/
Кроме старых добрых JSON-версий постов и профилей через наше маленькое API теперь можно запрашивать фид в формате JSONFeed и комментарии к постам.
Вот так можно получить фид: https://vas3k.club/feed.json
Вот так отфильтровать посты по рейтингу за год: https://vas3k.club/all/top_year/feed.json
Здесь получить данные конкретного поста: https://vas3k.club/post/2584.json
И вот так его комментарии: https://vas3k.club/post/2584/comments.json
Наш API всё еще маленький, но его идея в том, что он практически повторяет URL самого сайта, с добавлением расширения .json
или .md
для получения данных в нужном машиночитаемом формате.
Если вы будете пытаться делать это на других языках или фреймворках — не стесняйтесь приложить в комменты код или ссылку на гитхаб.
Во славу опенсорса
Кому интересно посмотреть как всё это дело работает на живом проекте на 10к человек изнутри, весь код своих пет-проджектов я пишу в опенсорс, здесь не исключение. Конечно, я херовая ролевая модель, но работает жи!
Провайдер:
- Модели: https://github.com/vas3k/vas3k.club/blob/master/authn/models/openid.py
- Вьюхи авторизации: https://github.com/vas3k/vas3k.club/blob/master/authn/views/openid.py
- Декораторы проверки привилегий (но там есть легаси, не делайте так): https://github.com/vas3k/vas3k.club/blob/master/authn/decorators/api.py
- PR (без описания и тестов, мы ведь СтАрТаП): https://github.com/vas3k/vas3k.club/pull/1058
Клиент:
- Авторизация и коллбеки: https://github.com/vas3k/vas3k.blog/blob/main/authn/views.py#L40
- Класс: https://github.com/vas3k/vas3k.blog/blob/main/authn/club.py#L9
Туториал для Flutter
это может быть внутренний WebVIew или редирект в браузер, не важно - авторизуемся в клуб и разрешаем доступ к нашему приложению
Мы получили ссылку вида CALLBACK?code=КОД
Офигенно!
Там при у картинки при пересылке на превью зерги буквы поели немного, вдруг важно
Кто такой этот Вастрик и почему он так много делает для клуба? 🤔
Тот момент, когда понимаешь, что @vas3k потихоньку переплевывает Цукерберга, и клуб уже потихоньку приближается к своей собственной метавселенной :)
Это здорово! Я пока не придумал, зачем мне это нужно, но сама возможность греет душу. )
@vas3k получается, сейчас нет возможности, через апишку забрать всех пользователей списком?
@vas3k, либо я что-то не так делаю, либо вход с нуля через openid не работает у тебя в блоге. Если разлогиниться в клубе и попробовать войти в блоге через клуб – будет вот такая вот ошибка.
(Я просто пытался на iOS завести, но тоже упирался в такую же ошибку)
Привет всем,
Всё тоже самое, но для связки .net 6 + Vue 3:
Код аутентификации для бэкенда:
Код для бэкенда
Обрабатываем коллбэк
Что в claims?
Доработки SPA
Вы прекрасны!