1. Introduction
Cet atelier de programmation s'appuie sur l'atelier de programmation Bouton "Se connecter avec Google" pour le Web. Assurez-vous donc de le terminer en premier.
Dans cet atelier de programmation, vous allez utiliser la bibliothèque JavaScript Google Identity Services et les invites One Tap pour ajouter la connexion des utilisateurs à des pages Web statiques et dynamiques à l'aide des API HTML et JavaScript.
Nous allons également configurer un point de terminaison de connexion côté serveur pour valider les jetons d'identité JWT.
Points abordés
- Configurer un point de terminaison de connexion côté serveur pour valider les jetons d'identité
- Ajouter une invite Se connecter avec Google One Tap à une page Web
- en tant qu'élément HTML statique ;
- de manière dynamique à l'aide de JavaScript.
- Comportement de l'invite S'identifier d'un seul geste
Prérequis
- Connaissances de base en HTML, CSS, JavaScript et Outils pour les développeurs Chrome (ou équivalent)
- Un endroit pour modifier et héberger des fichiers HTML et JavaScript.
- L'ID client obtenu dans l'atelier de programmation précédent.
- Un environnement capable d'exécuter une application Python de base.
C'est parti
2. Configurer un point de terminaison de connexion
Nous allons d'abord créer un script Python qui sert de serveur Web de base et configurer l'environnement Python nécessaire pour l'exécuter.
Lorsqu'il s'exécute en local, le script diffuse la page de destination, ainsi que les pages HTML statiques et One Tap dynamiques dans le navigateur. Il accepte les requêtes POST, décode le jeton JWT contenu dans le paramètre d'identifiant et vérifie qu'il a été émis par la plate-forme OAuth de Google Identity.
Après avoir décodé et validé un jeton JWT, le script redirige l'utilisateur vers la page de destination index.html
pour afficher les résultats.
Copiez-le dans un fichier nommé 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()
Étant donné que nous allons vérifier que le JWT a été émis pour votre client à l'aide du champ d'audience (aud) du jeton d'identité, votre application Python doit savoir quel ID client est utilisé. Pour ce faire, remplacez PUT_YOUR_WEB_CLIENT_ID_HERE
par l'ID client que vous avez utilisé dans l'atelier de programmation précédent sur le bouton "Se connecter avec Google".
Environnement Python
Configurons l'environnement pour exécuter le script du serveur Web.
Vous aurez besoin de Python 3.8 ou version ultérieure, ainsi que de quelques packages pour vous aider à vérifier et à décoder les jetons Web JSON (JWT).
$ python3 --version
Si votre version de Python 3 est antérieure à la version 3.8, vous devrez peut-être modifier le chemin d'accès de votre shell pour que la version attendue soit trouvée ou installer une version plus récente de Python sur votre système.
Ensuite, créez un fichier nommé requirements.txt
qui liste les packages dont nous avons besoin pour le décodage et la validation des jetons JWT :
google-auth
Exécutez ces commandes dans le même répertoire que simple-server.py
et requirements.txt
pour créer un environnement virtuel et installer des packages uniquement pour cette application :
$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install -r requirements.txt
Démarrez maintenant le serveur. Si tout fonctionne correctement, vous devriez voir ceci :
(env) $ python3 ./simple-server.py
Serving HTTP on 0.0.0.0 port 3000 (http://0.0.0.0:3000/) ...
IDE basés sur le cloud
Cet atelier de programmation a été conçu pour s'exécuter dans un terminal local et sur localhost, mais il peut être utilisé sur des plates-formes telles que Replit ou Glitch moyennant quelques modifications. Chaque plate-forme a ses propres exigences de configuration et ses propres valeurs par défaut pour l'environnement Python. Vous devrez donc probablement modifier quelques éléments, comme TARGET_HTML_PAGE_URL
et la configuration Python.
Par exemple, sur Glitch, vous devez toujours ajouter un requirements.txt
, mais vous devez également créer un fichier nommé start.sh
pour démarrer automatiquement le serveur Python :
python3 ./simple-server.py
Les URL utilisées par le script Python et les fichiers HTML doivent également être remplacées par l'URL externe de votre IDE Cloud. Nous aurons donc quelque chose comme TARGET_HTML_PAGE_URL = f"https://your-project-name.glitch.me/"
. Étant donné que les fichiers HTML de cet atelier de programmation utilisent également localhost par défaut, vous devrez les mettre à jour avec l'URL externe de Cloud IDE : data-login_uri="https://your-project-name.glitch.me/user-login"
.
3. Créer une page de destination
Nous allons ensuite créer une page de destination qui affichera les résultats de la connexion avec S'identifier en un clic. La page affiche le jeton d'identité JWT décodé ou une erreur. Un formulaire sur la page peut également être utilisé pour envoyer un jeton JWT au point de terminaison de connexion sur notre serveur HTTP Python, où il est décodé et vérifié. Il utilise un cookie CSRF à double envoi et un paramètre de requête POST afin de pouvoir réutiliser le même point de terminaison de serveur user-login
que les exemples d'API HTML et JavaScript du atelier de programmation.gsi/client
Dans votre terminal, enregistrez ce code dans un fichier nommé 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>
Tester le serveur Web et le décodage JWT
Avant d'essayer de travailler avec One Tap, nous allons nous assurer que l'environnement du point de terminaison du serveur est configuré et fonctionne.
Accédez à la page de destination http://localhost:3000/, puis appuyez sur le bouton Verify JWT (Vérifier le jeton JWT).
Vous devriez voir ceci
Lorsque l'utilisateur appuie sur le bouton, une requête POST est envoyée au script Python avec le contenu du champ de saisie. Le script s'attend à ce qu'un jeton JWT encodé soit présent dans le champ de saisie. Il tente donc de décoder et de valider la charge utile. Il redirige ensuite l'utilisateur vers la page de destination pour afficher les résultats.
Mais attendez, il n'y avait pas de jeton JWT… N'est-ce pas un échec ? Oui, mais en douceur !
Comme le champ était vide, une erreur s'affiche. Essayez maintenant de saisir du texte (n'importe quel texte) dans le champ de saisie et d'appuyer à nouveau sur le bouton. Elle échoue avec une erreur de décodage différente.
Vous pouvez coller un jeton d'identité JWT encodé émis par Google dans le champ de saisie et demander au script Python de le décoder, de le valider et de l'afficher pour vous. Vous pouvez également utiliser https://jwt.io pour inspecter n'importe quel jeton JWT encodé.
4. Page HTML statique
OK, nous allons maintenant configurer l'authentification One Tap pour qu'elle fonctionne sur les pages HTML sans utiliser de code JavaScript. Cela peut être utile pour les sites statiques ou pour les systèmes de mise en cache et les CDN.
Commencez par ajouter cet exemple de code dans un fichier nommé 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>
Ouvrez ensuite static-page.html
et remplacez PUT_YOUR_WEB_CLIENT_ID_HERE
par l'ID client que vous avez utilisé dans l'atelier de programmation précédent sur le bouton "Se connecter avec Google".
À quoi sert cette fonctionnalité ?
Tout élément HTML avec un id
de g_id_onload
et ses attributs de données sont utilisés pour configurer la bibliothèque Google Identity Services (gsi/client
). Il affiche également l'invite S'identifier en un clic lorsque le document est chargé dans le navigateur.
L'attribut data-login_uri
correspond à l'URI qui recevra une requête POST du navigateur une fois que l'utilisateur se sera connecté. Cette requête contient le jeton JWT encodé émis par Google.
Consultez le générateur de code HTML et la documentation de référence de l'API HTML pour obtenir la liste complète des options One Tap.
Se connecter
Cliquez sur http://localhost:3000/static-page.html.
L'invite S'identifier en un clic devrait s'afficher dans votre navigateur.
Appuyez sur Continuer en tant que pour vous connecter.
Une fois l'utilisateur connecté, Google envoie une requête POST au point de terminaison de connexion de votre serveur Python. La requête contient un jeton JWT encodé et signé par Google.
À partir de là, le serveur utilise l'une des clés de signature publiques de Google pour vérifier que Google a créé et signé le jeton JWT. Il décode ensuite l'audience et vérifie qu'elle correspond à votre ID client. Ensuite, une vérification CSRF est effectuée pour s'assurer que la valeur du cookie et celle du paramètre de requête dans le corps POST sont identiques. Si ce n'est pas le cas, c'est un signe de problème.
Enfin, la page de destination affiche le JWT validé sous la forme d'un identifiant utilisateur au format JSON.
Erreurs fréquentes
Il existe plusieurs raisons pour lesquelles le processus de connexion peut échouer. Voici quelques-unes des raisons les plus courantes :
data-client_id
est manquant ou incorrect. Dans ce cas, une erreur s'affiche dans la console d'outils de développement et l'invite S'identifier en un clic ne fonctionne pas.data-login_uri
n'est pas disponible, car un URI incorrect a été saisi, le serveur Web n'a pas été démarré ou il écoute sur le mauvais port. Dans ce cas, l'invite One Tap semble fonctionner, mais une erreur s'affiche dans l'onglet "Réseau" des outils de développement lorsque les identifiants sont renvoyés.- Le nom d'hôte ou le port utilisé par votre serveur Web ne figure pas dans la liste des origines JavaScript autorisées pour votre ID client OAuth. Le message de la console suivant s'affiche : Lors de la récupération du point de terminaison d'assertion d'identité, un code de réponse HTTP 400 a été reçu. Si vous voyez ce message pendant cet atelier de programmation, vérifiez que
http://localhost/
ethttp://localhost:3000
sont listés.
5. Page dynamique
Nous allons maintenant afficher l'authentification en un clic à l'aide d'un appel JavaScript. Dans cet exemple, nous afficherons toujours la connexion en un clic lorsque la page se chargera, mais vous pouvez choisir d'afficher l'invite uniquement lorsque cela est nécessaire. Par exemple, vous pouvez vérifier si la session de l'utilisateur date de plus de 28 jours et afficher à nouveau l'invite de connexion.
Ajoutez cet exemple de code dans un fichier nommé 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>
Ouvrez dynamic-page.html
et remplacez PUT_YOUR_WEB_CLIENT_ID_HERE
par l'ID client que vous avez utilisé dans l'atelier de programmation précédent sur le bouton "Se connecter avec Google".
Ce code est un mélange de HTML et de JavaScript. Il effectue plusieurs opérations :
- configure la bibliothèque Google Identity Services (
gsi/client
) en appelantgoogle.accounts.id.initialize()
. - génère un cookie CSRF (Cross-Site Request Forgery, falsification de requête intersites).
- ajoute un gestionnaire de rappel pour recevoir le jeton JWT encodé de Google et l'envoyer à l'aide d'un formulaire POST au point de terminaison de notre script Python
/user-login
. - affiche l'invite One Tap à l'aide de
google.accounts.id.prompt()
.
La liste complète des paramètres One Tap est disponible dans la documentation de référence de l'API JavaScript.
Connectons-nous !
Ouvrez http://localhost:3000/dynamic-page.html dans votre navigateur.
Le comportement de l'invite One Tap est le même que dans le scénario HTML statique, sauf que cette page définit un gestionnaire de rappel JavaScript pour créer un cookie CSRF, recevoir le JWT de Google et l'envoyer au point de terminaison user-login
du serveur Python. L'API HTML effectue ces étapes automatiquement pour vous.
6. Comportements des requêtes
Essayons donc certaines choses avec l'authentification One Tap, car contrairement au bouton, l'invite ne s'affiche pas toujours. Elle peut être ignorée, fermée ou désactivée par le navigateur et l'utilisateur.
Commencez par enregistrer le code ci-dessous dans un fichier nommé 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>
Ensuite, ouvrez prompt-outcomes.html
, remplacez PUT_YOUR_WEB_CLIENT_ID_HERE
par votre ID client, puis enregistrez le fichier.
Dans votre navigateur, ouvrez http://localhost:3000/prompt-outcomes.html.
Clics sur la page
Commencez par cliquer n'importe où en dehors de l'invite One Tap. Le message "La requête a été abandonnée." doit s'afficher sur la page et dans la console.
Sign-In
Ensuite, connectez-vous normalement. Vous verrez des mises à jour de journaux et de notifications qui peuvent être utilisées pour déclencher une action, comme l'établissement ou l'actualisation d'une session utilisateur.
Fermer la requête
Actualisez la page, puis appuyez sur le "X" dans la barre de titre de l'écran "S'inscrire en un clic" qui s'affiche. Le message suivant doit être consigné dans la console :
- L'utilisateur a refusé ou ignoré l'invite. Le refroidissement exponentiel de l'API a été déclenché."
Pendant les tests, vous déclencherez le refroidissement. Pendant la période de désactivation, l'invite One Tap ne s'affiche pas. Lors des tests, vous préférerez probablement réinitialiser l'appareil plutôt que d'attendre qu'il se réinitialise automatiquement… sauf si vous avez vraiment envie d'aller prendre un café ou de rentrer chez vous pour dormir. Pour réinitialiser le délai de récupération :
- Cliquez sur l'icône "Informations sur le site" à gauche de la barre d'adresse du navigateur.
- appuyez sur le bouton "Réinitialiser les autorisations".
- actualiser la page ;
Une fois le délai de récupération réinitialisé et la page rechargée, l'invite One Tap s'affiche.
7. Conclusion
Dans cet atelier de programmation, vous avez appris plusieurs choses, comme afficher One Tap en utilisant uniquement du code HTML statique ou de manière dynamique avec JavaScript.
Vous avez configuré un serveur Web Python très simple pour les tests locaux et appris les étapes nécessaires pour décoder et valider les jetons d'identité.
Vous avez testé les méthodes les plus courantes d'interaction et de fermeture de l'invite S'identifier en un clic, et vous disposez d'une page Web permettant de déboguer le comportement de l'invite.
Félicitations !
Pour obtenir des points supplémentaires, essayez de revenir en arrière et d'utiliser la connexion en un clic dans les différents navigateurs compatibles.
Les liens suivants peuvent vous aider à passer à l'étape suivante :
- API HTML Google Identity Services
- API JavaScript Google Identity Services
- Configurer Se connecter avec Google pour le Web
- Valider un jeton d'ID Google
- En savoir plus sur les projets Google Cloud
- Méthodes d'authentification de l'identité Google