1. Введение
Эта лабораторная работа основана на кнопке «Войти через Google» для веб -лабораторной работы, поэтому обязательно выполните ее в первую очередь.
В этой лабораторной работе вы будете использовать библиотеку JavaScript Google Identity Services и запросы One Tap для добавления входа пользователей на статические и динамические веб-страницы с помощью API HTML и JavaScript.
Мы также настроим конечную точку входа на стороне сервера для проверки токенов JWT ID.
Чему вы научитесь
- Как настроить конечную точку входа на стороне сервера для проверки идентификационных токенов
- Как добавить уведомление Google One Tap на веб-страницу
- как статический HTML-элемент, и
- динамически с использованием JavaScript.
- Как работает функция One Tap
Что вам понадобится
- Базовые знания HTML, CSS, JavaScript и Chrome DevTools (или эквивалент).
- Место для редактирования и размещения файлов 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 выдан вашему клиенту, используя поле аудитории (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-адрес вашей Cloud IDE. Таким образом, у нас получится что-то вроде: TARGET_HTML_PAGE_URL = f"https://your-project-name.glitch.me/"
Поскольку HTML-файлы в этой лабораторной работе также по умолчанию используют локальный хост, вам необходимо обновить их, указав внешний URL-адрес Cloud 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
, что и в примерах HTML и JavaScript API gsi/client
в практической работе.
В терминале сохраните это в файле с именем 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 ID, выданный 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
и его атрибутами data используется для настройки библиотеки 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 .
В вашем браузере должно отобразиться сообщение «Одно касание».
Нажмите «Продолжить», чтобы войти в систему.
После входа в систему Google отправляет POST-запрос на конечную точку входа вашего сервера Python. Запрос содержит закодированный JWT, подписанный Google.
Затем сервер использует один из открытых ключей подписи Google для подтверждения того, что Google создал и подписал JWT. Затем он декодирует и проверяет, соответствует ли аудитория вашему Client ID. Затем выполняется CSRF-проверка, чтобы убедиться, что значение Cookie и значение параметра запроса в теле POST совпадают. Если они не совпадают, это верный признак проблемы.
Наконец, на целевой странице отображается успешно проверенный JWT в виде учетных данных пользователя в формате JSON.
Распространенные ошибки
Существует несколько причин, по которым процесс входа может быть невозможен. Вот некоторые из наиболее распространённых причин:
-
data-client_id
отсутствует или неверен. В этом случае вы увидите ошибку в консоли DevTools, а приглашение One Tap не будет работать. -
data-login_uri
недоступен, поскольку был введён неверный URI, веб-сервер не запущен или прослушивает неверный порт. В этом случае запрос One Tap, по всей видимости, работает, но при возврате учётных данных на вкладке «Сеть» в DevTools отображается ошибка. - Имя хоста или порт, используемый вашим веб-сервером, не указан в списке авторизованных источников 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 в наш скрипт Python
/user-login
, и - отображает приглашение One Tap с помощью
google.accounts.id.prompt()
.
Полный список настроек One Tap можно найти в справочнике JavaScript API .
Давайте зарегистрируемся!
Откройте http://localhost:3000/dynamic-page.html в браузере.
Поведение запроса One Tap аналогично сценарию со статическим HTML, за исключением того, что на этой странице определяется обработчик обратного вызова JavaScript для создания CSRF-файла cookie, получения 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, нажмите на «X» в строке заголовка. В консоли должно появиться следующее сообщение:
- Пользователь отклонил или проигнорировал запрос. Запущено экспоненциальное торможение API.
Во время тестирования вы активируете заминку. В течение этого периода подсказка «Одним нажатием» не отображается. Во время тестирования вам, вероятно, захочется сбросить её, а не ждать автоматического сброса... если только вы не хотите выпить кофе или пойти домой поспать. Чтобы сбросить заминку:
- Нажмите значок «Информация о сайте» в левой части адресной строки браузера,
- нажмите кнопку «Сбросить разрешения» и
- перезагрузите страницу.
После сброса времени восстановления и перезагрузки страницы отобразится подсказка «Одно нажатие».
7. Заключение
Итак, в этой лабораторной работе вы узнали несколько вещей, например, как отображать One Tap, используя только статический HTML или динамически с помощью JavaScript.
Вы настроили очень простой веб-сервер Python для локального тестирования и изучили шаги, необходимые для декодирования и проверки идентификационных токенов.
Вы изучили наиболее распространенные способы взаимодействия пользователей с функцией One Tap и ее отклонения, а также создали веб-страницу, которую можно использовать для отладки поведения подсказок.
Поздравляю!
Для дополнительного балла попробуйте вернуться назад и использовать One Tap в разных браузерах, которые он поддерживает .
Эти ссылки могут помочь вам с дальнейшими шагами:
- HTML API служб идентификации Google
- API JavaScript служб идентификации Google
- Как настроить вход через Google для веб-сайтов
- Проверьте токен Google ID
- Узнайте больше о проектах Google Cloud
- Методы аутентификации Google Identity