1. Введение
Данный практический урок основан на практическом уроке «Вход через кнопку Google для веб- браузера», поэтому обязательно выполните его в первую очередь.
В этом практическом занятии вы будете использовать библиотеку JavaScript Google Identity Services и функцию One Tap prompts для добавления авторизации пользователей на статические и динамические веб-страницы с помощью API HTML и JavaScript.

Мы также настроим серверную точку входа для проверки JWT-токенов.
Что вы узнаете
- Как настроить серверную точку входа для проверки токенов идентификации.
- Как добавить запрос Google One Tap на веб-страницу
- в виде статического HTML-элемента, и
- динамически с использованием JavaScript.
- Как работает подсказка «Одно касание»
Что вам понадобится
- Базовые знания HTML, CSS, JavaScript и инструментов разработчика Chrome (или аналогичных).
- Место для редактирования и размещения HTML- и JavaScript-файлов.
- Идентификатор клиента, полученный в предыдущем практическом занятии.
- Среда, способная запускать базовое приложение на Python.
Пойдем!
2. Настройте точку входа в систему.
Сначала мы создадим скрипт на Python, который будет выступать в роли базового веб-сервера, и настроим необходимую среду Python для его запуска.
Запущенный локально, скрипт отображает в браузере целевую страницу, статические HTML-страницы и динамические страницы One Tap. Он принимает POST-запросы, декодирует JWT, содержащийся в параметре учетных данных, и проверяет, что он был выдан платформой OAuth от Google Identity.
После декодирования и проверки JWT скрипт перенаправляет на целевую страницу index.html для отображения результатов.
Скопируйте это в файл с именем simple-server.py :
"""Very basic web server to handle GET and POST requests."""
from http.server import SimpleHTTPRequestHandler
import json
import socketserver
from typing import Dict, Optional, Tuple
import urllib.parse
from urllib.parse import parse_qs
from google.auth.transport import requests as google_auth_requests
from google.oauth2 import id_token
""" NOTE: You'll need to change this """
CLIENT_ID = (
"PUT_YOUR_WEB_CLIENT_ID_HERE"
)
""" these may change for a Cloud IDE, but good as-is for local termainals """
SERVER_ADDRESS = "0.0.0.0"
PORT = 3000
TARGET_HTML_PAGE_URL = f"http://localhost:{PORT}/"
""" and this is the end of constants you might need to change """
HTTP_STATUS_OK = 200
HTTP_STATUS_BAD_REQUEST = 400
HTTP_STATUS_UNAUTHORIZED = 401
HTTP_STATUS_INTERNAL_SERVER_ERROR = 500
HTTP_STATUS_FOUND = 303 # For redirection after decode and verify
OIDC_SERVER = "accounts.google.com"
class OIDCJWTReceiver(SimpleHTTPRequestHandler):
"""Request handler to securely process a Google ID token response."""
def _validate_csrf(self, request_parameters: Dict) -> Tuple[bool, str]:
"""Validates the g_csrf_token to protect against CSRF attacks."""
csrf_token_body = request_parameters.get("g_csrf_token")
if not csrf_token_body:
return False, "g_csrf_token not found in POST body."
csrf_token_cookie = None
cookie_header = self.headers.get("Cookie")
if cookie_header:
cookie_pairs = (c.split("=", 1) for c in cookie_header.split(";"))
cookies = {k.strip(): v.strip() for k, v in cookie_pairs}
csrf_token_cookie = cookies.get("g_csrf_token")
if not csrf_token_cookie:
return False, "g_csrf_token not found in cookie."
if csrf_token_body != csrf_token_cookie:
return False, "CSRF token mismatch."
return True, "CSRF token validated successfully."
def _parse_and_validate_credential(
self, request_parameters: Dict
) -> Optional[Tuple[Optional[Dict], str]]:
"""Parse POST data, extract, decode and validate user credential."""
credential = request_parameters.get("credential")
if not credential:
return None, "Credential not provided"
try:
id_info = id_token.verify_oauth2_token(
credential, google_auth_requests.Request(), CLIENT_ID
)
return id_info, ""
except ValueError as e:
return None, f"Error during JWT decode: {e}"
except Exception as e:
return None, f"Unexpected error during credential validation: {e}"
def _redirect_to_html(self, response_data: Dict) -> None:
"""Redirect to the target HTML page with data in the URL fragment."""
try:
json_data = json.dumps(response_data)
encoded_data = urllib.parse.quote(json_data)
redirect_url = f"http://localhost:{PORT}/#data={encoded_data}"
self.send_response(HTTP_STATUS_FOUND)
self.send_header("Location", redirect_url)
self.send_header("Connection", "close")
self.end_headers()
except Exception as e:
print(f"An error occurred during redirection: {e}")
self.send_response(HTTP_STATUS_INTERNAL_SERVER_ERROR)
self.send_header("Content-type", "text/plain")
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(f"A redirect error occurred: {e}".encode("utf-8"))
def _send_bad_request(self, message: str) -> None:
"""Sends a 400 Bad Request response."""
self.send_response(HTTP_STATUS_BAD_REQUEST)
self.send_header("Content-type", "text/plain")
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(message.encode("utf-8"))
def do_POST(self):
"""Handle POST requests for the /user-login path."""
if self.path != "/user-login":
self.send_error(404, "File not found")
return
try:
content_length = int(self.headers.get("Content-Length", 0))
post_data_bytes = self.rfile.read(content_length)
post_data_str = post_data_bytes.decode("utf-8")
request_parameters = {
key: val[0]
for key, val in parse_qs(post_data_str).items()
if len(val) == 1
}
csrf_valid, csrf_message = self._validate_csrf(request_parameters)
if not csrf_valid:
print(f"CSRF verify failure: {csrf_message}")
self._send_bad_request(f"CSRF verify failure: {csrf_message}")
return
decoded_id_token, error_message = self._parse_and_validate_credential(
request_parameters
)
response_data = {}
if decoded_id_token:
response_data["status"] = "success"
response_data["message"] = decoded_id_token
elif error_message:
response_data["status"] = "error"
response_data["message"] = error_message
else:
response_data["status"] = "error"
response_data["message"] = "Unknown error during JWT validation"
self._redirect_to_html(response_data)
except Exception as e:
self._redirect_to_html(
{"status": "error", "error_message": f"Internal server error: {e}"}
)
with socketserver.TCPServer(("", PORT), OIDCJWTReceiver) as httpd:
print(
f"Serving HTTP on {SERVER_ADDRESS} port"
f" {PORT} (http://{SERVER_ADDRESS}:{PORT}/)"
)
httpd.serve_forever()
Поскольку мы собираемся проверить, был ли JWT выдан вашему клиенту, используя поле audience (aud) токена ID, вашему приложению на Python необходимо знать, какой идентификатор клиента используется. Для этого замените PUT_YOUR_WEB_CLIENT_ID_HERE на идентификатор клиента, который вы использовали в предыдущем практическом задании по созданию кнопки «Войти через Google».
среда Python
Давайте настроим среду для запуска скрипта веб-сервера.
Вам потребуется Python 3.8 или более поздней версии, а также несколько пакетов для проверки и декодирования JWT.
$ python3 --version
Если ваша версия Python3 ниже 3.8, возможно, вам потребуется изменить переменную PATH в вашей оболочке, чтобы она отображала ожидаемую версию, или установить более новую версию Python в вашей системе.
Далее создайте файл с именем requirements.txt , в котором будет перечислены пакеты, необходимые для декодирования и проверки JWT:
google-auth
Выполните эти команды в той же директории, что и файлы simple-server.py и requirements.txt , чтобы создать виртуальное окружение и установить пакеты только для этого приложения:
$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install -r requirements.txt
Теперь запустите сервер, и если всё работает правильно, вы увидите следующее:
(env) $ python3 ./simple-server.py
Serving HTTP on 0.0.0.0 port 3000 (http://0.0.0.0:3000/) ...
Облачные IDE
Данный практический пример разработан для работы в локальном терминале и на локальном компьютере, но с некоторыми изменениями его можно использовать на таких платформах, как Replit или Glitch. Каждая платформа имеет свои собственные требования к настройке и параметры среды Python по умолчанию, поэтому вам, вероятно, потребуется изменить некоторые параметры, такие как TARGET_HTML_PAGE_URL и настройки Python.
Например, в Glitch вам по-прежнему потребуется добавить файл requirements.txt , но также создать файл с именем start.sh для автоматического запуска сервера Python:
python3 ./simple-server.py
URL-адреса, используемые скриптом Python и HTML-файлами, также необходимо обновить, указав внешний URL-адрес вашей облачной IDE. Таким образом, у нас получится что-то вроде этого: TARGET_HTML_PAGE_URL = f"https://your-project-name.glitch.me/" , а поскольку HTML-файлы в этом примере по умолчанию используют localhost, вам нужно будет обновить их, указав внешний URL-адрес облачной IDE: data-login_uri="https://your-project-name.glitch.me/user-login" .
3. Создайте целевую страницу.
Далее мы создадим целевую страницу, которая отображает результаты входа в систему с помощью One Tap. На странице отображается расшифрованный JWT-токен ID или сообщение об ошибке. Форма на странице также может использоваться для отправки JWT на конечную точку входа на нашем HTTP-сервере Python, где он будет расшифрован и проверен. Она использует cookie с защитой от двойной отправки CSRF и параметр запроса POST, что позволяет повторно использовать ту же конечную точку сервера user-login что и в примерах gsi/client HTML и JavaScript API в практическом руководстве.
В терминале сохраните этот код в файл с именем index.html :
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JWT Verification</title>
<style>
body { font-family: sans-serif; margin: 0; }
.top-nav {
text-align: center; padding: 15px 0; background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6; width: 100%;
}
.top-nav a {
margin: 0 15px; text-decoration: none; font-weight: bold;
color: #007bff; font-size: 1.1em;
}
.top-nav a:hover { text-decoration: underline; }
.page-container { padding: 20px; }
pre {
background-color: #f9f9f9; padding: 10px; overflow-x: auto;
white-space: pre-wrap; word-break: break-all;
}
.error { color: red; }
.success { color: green; }
#jwt-form { margin-bottom: 20px; flex: 1; }
fieldset {
border: 1px solid #ddd; padding: 10px; margin: 0;
min-width: 0; flex: 1; }
legend { font-weight: bold; margin-bottom: 5px; }
textarea { width: 100%; box-sizing: border-box; vertical-align: top; }
button[type="submit"] { padding: 8px 15px; margin-top: 10px; }
@media (min-width: 1024px) {
.main-content { display: flex; gap: 20px; }
.main-content > #jwt-form,
.main-content > .result-container {
flex-basis: 50%; /* Each item takes up 50% of the width */
margin-bottom: 0; /* Remove bottom margin when side-by-side */
display: flex; /* Make the result container a flex container */
flex-direction: column; /* Stack children vertically */
flex: 1; /* Allows the result container to grow and shrink */
}
}
</style>
</head>
<body>
<nav class="top-nav">
<a href="static-page.html">One Tap Static Page</a>
<a href="dynamic-page.html">One Tap Dynamic Page</a>
<a href="prompt-outcomes.html">Prompt behaviors</a>
</nav>
<div class="page-container">
<h1>JWT Verification</h1>
<div class="main-content">
<form id="jwt-form" action="/user-login" method="post">
<fieldset>
<legend>Encoded JWT ID Token</legend>
<textarea id="credential" name="credential" rows="5"
cols="50"></textarea>
<button type="submit">Verify JWT</button>
</fieldset>
</form>
<section class="result-container">
<fieldset>
<legend>Decode and verify result</legend>
<p id="status"></p>
<pre id="result"></pre>
</fieldset>
</section>
</div>
</div>
<script>
const statusElement = document.getElementById("status");
const resultElement = document.getElementById("result");
const handleResponse = (responseData) => {
const { status, message } = responseData;
const result = message
? status === "success"
? JSON.stringify(message, null, 2)
: message
: "";
statusElement.textContent = status;
resultElement.textContent = result;
statusElement.className = "";
if (status === "success") {
statusElement.classList.add("success");
} else if (status === "error") {
statusElement.classList.add("error");
}
};
const getEncodedDataFromHash = (hash) => {
const urlParams = new URLSearchParams(hash.substring(1));
return urlParams.get("data");
};
const processHashData = (hash) => {
const encodedData = getEncodedDataFromHash(hash);
if (encodedData) {
try {
const jsonData = JSON.parse(decodeURIComponent(encodedData));
handleResponse(jsonData);
history.pushState(
"",
document.title,
window.location.pathname + window.location.search,
);
} catch (error) {
handleResponse({
status: "error",
message: "Error parsing data from URL: " + error,
});
}
}
};
window.addEventListener("load",
() => processHashData(window.location.hash));
window.addEventListener("hashchange",
() => processHashData(window.location.hash));
</script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const generateRandomString = (length) => {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789";
let result = "";
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};
const csrfToken = generateRandomString(12);
document.cookie = `g_csrf_token=${csrfToken};path=/;SameSite=Lax`;
const form = document.getElementById("jwt-form");
const hiddenInput = document.createElement("input");
hiddenInput.setAttribute("type", "hidden");
hiddenInput.setAttribute("name", "g_csrf_token");
hiddenInput.setAttribute("value", csrfToken);
form.appendChild(hiddenInput);
});
</script>
</body>
</html>
Проверьте работу веб-сервера и декодирование JWT.
Прежде чем начать работу с One Tap, мы убедимся, что среда серверной конечной точки настроена и функционирует должным образом.
Перейдите на главную страницу по адресу http://localhost:3000/ и нажмите кнопку «Проверить JWT» .
Вам стоит это увидеть

Нажатие кнопки отправляет POST-запрос с содержимым поля ввода в скрипт Python. Скрипт ожидает наличия закодированного JWT в поле ввода, поэтому он пытается декодировать и проверить полезную нагрузку. После этого он перенаправляет пользователя обратно на целевую страницу для отображения результатов.
Но подождите, разве JWT не существовало... разве это не провалилось? Да, но изящно!
Поскольку поле было пустым, отображается ошибка. Теперь попробуйте ввести какой-нибудь текст (любой текст) в поле ввода и снова нажать кнопку. Это не удастся, выдав другую ошибку декодирования.
Вы можете вставить закодированный JWT-токен, выданный Google, в поле ввода, и скрипт Python расшифрует, проверит и отобразит его для вас... или вы можете использовать https://jwt.io для проверки любого закодированного JWT.
4. Статическая HTML-страница
Хорошо, теперь мы настроим One Tap для работы с HTML-страницами без использования JavaScript. Это может быть полезно для статических сайтов, а также для систем кэширования и CDN.
Для начала добавьте этот пример кода в файл с именем static-page.html :
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://accounts.google.com/gsi/client" async></script>
<link rel="icon" href="data:," />
</head>
<body>
<h1>Google One Tap static HTML page</h1>
<div
id="g_id_onload"
data-client_id="PUT_YOUR_WEB_CLIENT_ID_HERE"
data-ux_mode="redirect"
data-login_uri="http://localhost:3000/user-login"
></div>
</body>
</html>
Далее откройте static-page.html и замените PUT_YOUR_WEB_CLIENT_ID_HERE на идентификатор клиента, который вы использовали в предыдущем практическом задании по созданию кнопки «Войти через Google».
Так что же это дает?
Любой HTML-элемент с id g_id_onload и его атрибутами данных используется для настройки библиотеки Google Identity Services ( gsi/client ). Он также отображает запрос One Tap при загрузке документа в браузере.
Атрибут data-login_uri — это URI, на который браузер отправит POST-запрос после входа пользователя в систему. Этот запрос содержит закодированный JWT-токен, выданный Google.
Для получения полного списка опций One Tap воспользуйтесь генератором HTML-кода и справочником по HTML API .
Войти
Перейдите по ссылке http://localhost:3000/static-page.html .
В вашем браузере должно отобразиться сообщение One Tap.

Нажмите «Продолжить», чтобы войти в систему.
После авторизации Google отправляет POST-запрос на конечную точку входа вашего Python-сервера. Запрос содержит закодированный JWT, подписанный Google.
Затем сервер использует один из открытых ключей подписи Google, чтобы убедиться, что Google создал и подписал JWT. После этого он декодирует и проверяет, соответствует ли аудитория вашему идентификатору клиента. Далее выполняется проверка на CSRF-атаки, чтобы убедиться, что значение файла cookie и значение параметра запроса в теле POST-запроса совпадают. Если они не совпадают, это верный признак проблемы.
В заключение, на главной странице отображается успешно подтвержденный JWT в виде учетных данных пользователя в формате JSON, представляющих собой идентификационный токен.

Распространенные ошибки
Процесс авторизации может завершиться сбоем по нескольким причинам. Вот некоторые из наиболее распространенных:
- Если
data-client_idотсутствует или указан неверно, в этом случае вы увидите ошибку в консоли DevTools, и запрос One Tap работать не будет. -
data-login_uriнедоступен, поскольку был введен некорректный URI, веб-сервер не запущен или прослушивает неправильный порт. В этом случае запрос One Tap будет отображаться корректно, но при возврате учетных данных на вкладке «Сеть» в инструментах разработчика появится сообщение об ошибке. - Имя хоста или порт, используемый вашим веб-сервером, не указаны в списке разрешенных источников JavaScript для вашего идентификатора клиента OAuth . Вы увидите сообщение в консоли: « При получении конечной точки утверждения идентификатора был получен код ответа HTTP 400 ». Если вы видите это во время выполнения этого практического задания, убедитесь, что указаны как
http://localhost/так иhttp://localhost:3000.
5. Динамическая страница
Теперь мы отобразим One Tap с помощью вызова JavaScript. В этом примере мы будем всегда отображать One Tap при загрузке страницы, но вы можете выбрать отображение запроса только при необходимости. Например, вы можете проверить, если сессия пользователя старше 28 дней, и снова отобразить запрос на вход.
Добавьте этот пример кода в файл с именем dynamic-page.html .
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://accounts.google.com/gsi/client" async></script>
<link rel="icon" href="data:," />
</head>
<body>
<h1>Google One Tap dynamic page</h1>
<script>
const generateRandomString = (length) => {
const array = new Uint8Array(length / 2);
window.crypto.getRandomValues(array);
return Array.from(array, (byte) =>
byte.toString(16).padStart(2, "0")
).join("");
};
const setCookie = (name, value) => {
document.cookie = '${name}=${value};path=/;SameSite=Lax';
};
const getCookie = (name) => {
const nameEQ = name + "=";
const ca = document.cookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == " ") c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0)
return c.substring(nameEQ.length, c.length);
}
return null;
};
function handleResponse(rsp) {
console.log("ID Token received from Google: ", rsp.credential);
console.log("Submitting token to server via dynamic form POST.");
const form = document.createElement("form");
form.method = "POST";
form.action = "http://" + window.location.host + "/user-login";
// Add the credential and CSRF cookie value asa hidden fields
const hiddenField = document.createElement("input");
hiddenField.type = "hidden";
hiddenField.name = "credential";
hiddenField.value = rsp.credential;
form.appendChild(hiddenField);
const csrfToken = getCookie("g_csrf_token");
if (csrfToken) {
console.log("Found g_csrf_token cookie, adding to form.");
const csrfField = document.createElement("input");
csrfField.type = "hidden";
csrfField.name = "g_csrf_token";
csrfField.value = csrfToken;
form.appendChild(csrfField);
} else {
console.warn(
"Warning: g_csrf_token cookie not found. POSTing without it."
);
}
document.body.appendChild(form);
form.submit();
}
window.onload = function () {
const csrfToken = generateRandomString(12);
setCookie("g_csrf_token", csrfToken);
console.log("CSRF token cookie set on page load:", csrfToken);
google.accounts.id.initialize({
client_id: "PUT_YOUR_WEB_CLIENT_ID_HERE",
ux_mode: "popup",
callback: handleResponse,
});
google.accounts.id.prompt(); // Display the One Tap prompt
};
</script>
</body>
</html>
Откройте dynamic-page.html и замените PUT_YOUR_WEB_CLIENT_ID_HERE на идентификатор клиента, который вы использовали в предыдущем практическом задании по созданию кнопки «Войти через Google».
Этот код представляет собой смесь HTML и JavaScript и выполняет несколько функций:
- Настраивает библиотеку Google Identity Services (
gsi/client), вызывая методgoogle.accounts.id.initialize(). - генерирует cookie-файл, защищающий от межсайтовой подделки запросов ( CSRF ).
- добавляет обработчик обратного вызова для получения закодированного JWT от Google и отправки его методом POST с помощью формы на конечную точку
/user-loginнашего скрипта на Python. - Отображает запрос One Tap с помощью
google.accounts.id.prompt().
Полный список настроек One Tap можно найти в справочнике по JavaScript API .
Давайте войдем в систему!
Откройте в браузере страницу http://localhost:3000/dynamic-page.html .
Поведение запроса One Tap аналогично сценарию со статическим HTML, за исключением того, что на этой странице определен обработчик обратного вызова JavaScript для создания CSRF-куки, получения JWT от Google и отправки его методом POST на конечную точку user-login на сервере Python. HTML API выполняет эти шаги автоматически.

6. Побуждающее поведение
Давайте попробуем кое-что сделать с функцией One Tap, потому что, в отличие от кнопки, подсказка отображается не всегда. Ее можно закрыть, удалить или отключить браузером и пользователем.
Сначала сохраните это в файле с именем prompt-outcomes.html :
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Google One Tap Prompt behaviors</title>
<style>
body { font-family: sans-serif; padding: 20px; }
#log {
border: 1px solid #ccc; background-color: #f0f0f0;
padding: 15px; margin-top: 20px;
white-space: pre-wrap; font-family: monospace;
}
.success { color: green; }
.error { color: red; }
.info { color: blue; }
.warning { color: orange; }
</style>
</head>
<body>
<h1>One Tap behaviors</h1>
<p>Open the developer console to see detailed logs.</p>
<div id="log">Awaiting events...</div>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<script>
// logging utility to display event and notification info
const logElement = document.getElementById("log");
function log(message, type = "info") {
const timestamp = new Date().toLocaleTimeString();
logElement.innerHTML +=
`\n<span class="${type}">[${timestamp}] ${message}</span>`;
console.log(`[${type.toUpperCase()}] ${message}`);
}
function decodeJwt(jwt) {
try {
const parts = jwt.split(".");
if (parts.length !== 3) {
throw new Error("Invalid JWT structure");
}
const base64Url = parts[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const jsonPayload = decodeURIComponent(
atob(base64)
.split("")
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join(""),
);
return JSON.parse(jsonPayload);
} catch (e) {
log(`Error decoding JWT: ${e.message}`, "error");
return null;
}
}
/* Handles the credential response after a user signs in. */
function handleCredentialResponse(credentialResponse) {
log("Credential Response received.", "success");
const credential = credentialResponse.credential;
log(`Credential JWT: ${credential.substring(0, 30)}...`);
// For demonstration, we decode the JWT on the client side.
// REMEMBER: Always verify the token on your backend server!
const payload = decodeJwt(credential);
if (payload) {
log(`Welcome, ${payload.name}! (Email: ${payload.email})`);
log("Decoded JWT Payload: " + JSON.stringify(payload, null, 2));
}
}
/* Handles notifications about the One-Tap prompt's UI status. */
function handlePromptMomentNotification(notification) {
log(`Prompt Moment Notification received.`, "info");
if (notification.isNotDisplayed()) {
const reason = notification.getNotDisplayedReason();
log(`Prompt not displayed. Reason: <strong>${reason}</strong>`,
"error");
}
if (notification.isSkippedMoment()) {
const reason = notification.getSkippedReason();
log(`Prompt was skipped. Reason: <strong>${reason}</strong>`,
"warning");
if (reason === "auto_cancel") {
log("may have called prompt() multiple times in a row.");
} else if (reason === "user_cancel") {
log("The user manually closed the prompt.");
}
}
if (notification.isDismissedMoment()) {
const reason = notification.getDismissedReason();
log(`Prompt dismissed. Reason: <strong>${reason}</strong>`, "info");
if (reason === "credential_returned") {
log("Expected, credential sent to the JS handler.");
} else if (reason === "cancel_called") {
log("programmatic call to google.accounts.id.cancel().");
}
}
}
window.onload = function () {
try {
google.accounts.id.initialize({
client_id: "PUT_YOUR_WEB_CLIENT_ID_HERE",
callback: handleCredentialResponse,
ux_mode: "popup",
});
google.accounts.id.prompt(handlePromptMomentNotification);
log("One Tap initialized. Waiting for prompt...");
} catch (e) {
log(`Initialization Error: ${e.message}`, "error");
}
};
</script>
</body>
</html>
Далее откройте prompt-outcomes.html , замените PUT_YOUR_WEB_CLIENT_ID_HERE на ваш идентификатор клиента, а затем сохраните файл.
Откройте в браузере страницу http://localhost:3000/prompt-outcomes.html
клики по странице
Для начала щелкните в любом месте за пределами окна One Tap. На странице и в консоли должно отобразиться сообщение « Запрос прерван».
Войти
Далее просто войдите в систему обычным способом. Вы увидите обновления в журнале и уведомления, которые можно использовать для запуска таких действий, как установление или обновление пользовательской сессии.
Закройте окно запроса
Теперь перезагрузите страницу, и после того, как отобразится One Tap, нажмите на крестик в заголовке страницы. В консоль должно быть выведено следующее сообщение:
- Пользователь отклонил или закрыл запрос. Запущено экспоненциальное замедление работы API.
Во время тестирования запустится период ожидания. В течение этого периода подсказка «Одно касание» не отображается. Во время тестирования вы, вероятно, захотите сбросить его, а не ждать автоматического сброса... если, конечно, вам не очень хочется пойти выпить кофе или домой и поспать. Чтобы сбросить период ожидания:
- Нажмите на значок «Информация о сайте» в левой части адресной строки браузера.
- Нажмите кнопку "Сбросить разрешения", и
- Перезагрузите страницу.
После сброса времени ожидания и перезагрузки страницы отобразится подсказка «Одно касание».
7. Заключение
В этом практическом занятии вы узнали несколько вещей, например, как отобразить событие One Tap, используя только статический HTML или динамически с помощью JavaScript.
Вы настроили очень простой веб-сервер на Python для локального тестирования и изучили шаги, необходимые для декодирования и проверки идентификационных токенов.
Вы изучили наиболее распространенные способы взаимодействия пользователей с запросом One Tap и его закрытия, а также создали веб-страницу, которую можно использовать для отладки поведения запроса.
Поздравляем!
В качестве дополнительного задания попробуйте еще раз протестировать One Tap в разных браузерах, которые он поддерживает .
Эти ссылки могут помочь вам с дальнейшими шагами:
- HTML API Google Identity Services
- API JavaScript для служб идентификации Google
- Как настроить вход в веб-версию через Google
- Подтвердите токен Google ID.
- Узнайте больше о проектах Google Cloud.
- Методы аутентификации Google Identity