Где открывать страницу входа
Поток один и тот же в любом контейнере: код всегда приходит на ваш redirect_uri, и обменять его
может только ваш сервер. Браузер — вкладка, всплывающее окно или WebView — лишь место, где
пользователь входит.
| Контейнер | Статус | Как |
|---|---|---|
| Переход в той же вкладке | поддерживается | Самый простой вариант: перенаправьте на /authorize, получите пользователя на redirect_uri. |
| Всплывающее окно | поддерживается | Ваша страница остаётся открытой. Рецепт ниже. |
| Мобильный WebView (WKWebView / Android WebView) | поддерживается | Откройте /authorize в WebView и перехватите переход на redirect_uri. Режим embed=1 без подвала — см. параметры ниже. |
<iframe> на вашем сайте | запрещено | Страница входа не встраивается в чужие сайты — это защита от кликджекинга. Используйте всплывающее окно: в нём видна адресная строка, а это и делает страницу входа заслуживающей доверия. |
Всплывающее окно
Открывайте окно прямо в обработчике клика (окно, открытое позже, заблокирует браузер) и ведите
его на /authorize, а не на страницу входа напрямую — именно /authorize создаёт запрос на вход.
const POPUP = 'width=460,height=720,menubar=no,toolbar=no,location=yes'
button.addEventListener('click', async () => {
// ваш сервер строит ссылку /authorize: у него client_id, redirect_uri и state, который он потом проверит
const { url } = await fetch('/auth/start', { method: 'POST' }).then((r) => r.json())
const popup = window.open(url, 'verificahub-id', POPUP)
window.addEventListener('message', (e) => {
if (e.origin !== 'https://id.verificahub.ru') return // всегда проверяйте origin
if (e.data?.source !== 'verificahub-id') return
if (e.data.type === 'verified') showSpinner() // только сигнал интерфейсу — см. ниже
})
})
Потом окно переходит на ваш redirect_uri, и закрывает его ваша страница callback:
<!-- ваш /auth/callback, открытый в окне после успешного обмена кода на сервере -->
<script>
window.opener?.postMessage({ type: 'login-complete' }, window.location.origin)
window.close()
</script>
Учитывайте, что пользователь может закрыть окно сам: проверяйте popup.closed или просто оставьте
кнопку входа активной.
Сообщение verified
Перед возвратом страница отправляет в window.opener (и window.parent):
{ source: 'verificahub-id', type: 'verified' }
В нём нет ни кода, ни номера: сообщение уходит с targetOrigin: '*', и всё, что в нём есть,
могло бы прочитать любое окно в цепочке. Это сигнал интерфейсу («уберите кнопку, покажите
загрузку»), а не результат. Результат — то, что ваш сервер получил от /exchange.
Параметры страницы
Передайте их в /authorize — он перенесёт их на страницу входа. Значения вне списка отбрасываются.
Параметр /authorize | Что делает |
|---|---|
ui_locales=ru|en (или lang) | Язык страницы. По умолчанию — defaultLocale клиента, иначе русский. |
theme=light|dark | Закрепляет тему — для приложений, чья тема не совпадает с системной. По умолчанию страница следует теме устройства. |
display=touch (или embed=1) | Без подвала — для WebView, где рамку рисует приложение. |
Размеры
- Всплывающее окно: удобно 460 × 720, работает от ~360 × 560. В низком окне страница уплотняется, а если не помещается — прокручивается.
- WebView: дайте хотя бы 360 px ширины. Поля ввода не меньше 16 px, поэтому iOS не увеличивает страницу при фокусе; вырезы экрана и полоса «домой» учитываются.
- Учитываются
prefers-reduced-motionи режим высокой контрастности Windows.
Что гарантирует страница
- Не видит ваш
client_secretи не меняет код на номер — это делает только ваш сервер. - Без cookies: доступ даёт одноразовая ссылка, поэтому потерянная ссылка просто истекает.
- Номер на странице хранится только в этой вкладке и только до конца входа — чтобы после перезагрузки можно было продолжить с ввода кода.
- Заголовки: HSTS,
Referrer-Policy: no-referrer,X-Content-Type-Options: nosniff; встраивание в чужие сайты запрещено; страница входа закрыта от индексации.