import { useRef, useState } from "react"; import { clsx } from "keycloakify/tools/clsx"; import type { MessageKey } from "keycloakify/login/i18n/i18n"; import { base64url } from "rfc4648"; import { useConstCallback } from "keycloakify/tools/useConstCallback"; import { type PageProps, defaultClasses } from "keycloakify/login/pages/PageProps"; import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; export default function WebauthnAuthenticate(props: PageProps, I18n>) { const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; const { getClassName } = useGetClassName({ "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses, classes }); const { url } = kcContext; const { msg, msgStr } = i18n; const { authenticators, challenge, shouldDisplayAuthenticators, userVerification, rpId } = kcContext; const createTimeout = Number(kcContext.createTimeout); const isUserIdentified = kcContext.isUserIdentified == "true"; const webAuthnAuthenticate = useConstCallback(async () => { if (!isUserIdentified) { return; } const allowCredentials = authenticators.authenticators.map( authenticator => ({ id: base64url.parse(authenticator.credentialId, { loose: true }), type: "public-key" } as PublicKeyCredentialDescriptor) ); // Check if WebAuthn is supported by this browser if (!window.PublicKeyCredential) { setError(msgStr("webauthn-unsupported-browser-text")); submitForm(); return; } const publicKey: PublicKeyCredentialRequestOptions = { rpId, challenge: base64url.parse(challenge, { loose: true }) }; if (createTimeout !== 0) { publicKey.timeout = createTimeout * 1000; } if (allowCredentials.length) { publicKey.allowCredentials = allowCredentials; } if (userVerification !== "not specified") { publicKey.userVerification = userVerification; } try { const resultRaw = await navigator.credentials.get({ publicKey }); if (!resultRaw || resultRaw.type != "public-key") return; const result = resultRaw as PublicKeyCredential; if (!("authenticatorData" in result.response)) return; const response = result.response as AuthenticatorAssertionResponse; const clientDataJSON = response.clientDataJSON; const authenticatorData = response.authenticatorData; const signature = response.signature; setClientDataJSON(base64url.stringify(new Uint8Array(clientDataJSON), { pad: false })); setAuthenticatorData(base64url.stringify(new Uint8Array(authenticatorData), { pad: false })); setSignature(base64url.stringify(new Uint8Array(signature), { pad: false })); setCredentialId(result.id); setUserHandle(base64url.stringify(new Uint8Array(response.userHandle!), { pad: false })); submitForm(); } catch (err) { setError(String(err)); submitForm(); } }); const webAuthForm = useRef(null); const submitForm = useConstCallback(() => { webAuthForm.current!.submit(); }); const [clientDataJSON, setClientDataJSON] = useState(""); const [authenticatorData, setAuthenticatorData] = useState(""); const [signature, setSignature] = useState(""); const [credentialId, setCredentialId] = useState(""); const [userHandle, setUserHandle] = useState(""); const [error, setError] = useState(""); return (