---
url: 'https://www.corbado.com/it/blog/tutorial-passkey-come-implementare-le-passkey'
title: 'Tutorial sulle passkey: come implementare le passkey nelle app web'
description: 'Questo tutorial spiega come implementare le passkey nella tua app web. Utilizziamo Node.js (TypeScript), SimpleWebAuthn, HTML/JavaScript Vanilla e MySQL.'
lang: 'it'
author: 'Vincent Delitz'
date: '2025-06-17T16:16:24.608Z'
lastModified: '2026-03-25T10:05:40.808Z'
keywords: 'tutorial passkey, implementare passkey, guida passkey'
category: 'Passkeys Implementation'
---

# Tutorial sulle passkey: come implementare le passkey nelle app web

## 1. Introduzione: come implementare le passkey

In questo tutorial, ti aiuteremo nei tuoi sforzi di implementazione delle passkey,
offrendo una guida passo-passo su come aggiungere le passkey al tuo sito web.

Avere un'[autenticazione](https://www.corbado.com/it/blog/come-passare-autenticazione-completamente-passwordless)
moderna, robusta e di facile utilizzo è fondamentale quando si vuole creare un ottimo sito
web o un'app. Le passkey sono emerse come la risposta a questa sfida. Fungendo da nuovo
standard per gli accessi, promettono un futuro senza gli svantaggi delle password
tradizionali, fornendo un'esperienza di accesso veramente senza password (che non è solo
sicura ma anche estremamente comoda).

Ciò che esprime veramente il potenziale delle passkey è il sostegno che hanno raccolto.
Tutti i browser più importanti, che si tratti di Chrome, Firefox, Safari o Edge, e tutti i
principali produttori di dispositivi (Apple, Microsoft, Google) hanno incorporato il
supporto. Questa adozione unanime dimostra che le passkey sono il nuovo standard per gli
accessi.

Sì, esistono già tutorial sull'integrazione delle passkey nelle applicazioni web. Che si
tratti di framework frontend come [React](https://www.corbado.com/blog/react-passkeys),
[Vue.js](https://www.corbado.com/blog/vuejs-passkeys) o [Next.js](https://www.corbado.com/blog/nextjs-passkeys), c'è una pletora di
guide progettate per mitigare le sfide e accelerare le implementazioni delle passkey.
Tuttavia, manca un **tutorial end-to-end** che rimanga **minimalista e di base**. Molti
sviluppatori ci hanno contattato e chiesto un tutorial che facesse chiarezza
sull'**implementazione delle passkey per le app web**.

Questo è esattamente il motivo per cui abbiamo creato questa guida. Il nostro obiettivo?
Creare una configurazione minima funzionante per le passkey, che comprenda il **livello
frontend, backend e database** (quest'ultimo spesso trascurato anche se può causare seri
grattacapi).

Alla fine di questo percorso, avrai costruito un'applicazione web minima funzionante, in
cui potrai:

- [Creare una passkey](https://www.corbado.com/it/blog/migliori-pratiche-creazione-passkey)
- Usare la passkey per accedere

Per chi ha fretta o desidera un riferimento, l'intero codice sorgente è disponibile su
[GitHub](https://github.com/corbado/passkey-tutorial).

Curioso di vedere come sarà il risultato finale? Ecco un'anteprima del progetto finale
(ammettiamo che sembra molto basilare, ma le
[cose](https://www.corbado.com/blog/webauthn-pubkeycredparams-credentialpublickey) interessanti sono sotto la
superficie):

![Schermata di accesso del tutorial sulle passkey](https://www.corbado.com/website-assets/6572cd2ed5d547903c8ad74d_passkey_tutorial_register_login_screen_f55a0f5ae9.png)

Siamo pienamente consapevoli che parti del codice e del progetto possono essere realizzate
in modo diverso o più sofisticato, ma volevamo concentrarci sull'essenziale. Ecco perché
abbiamo intenzionalmente mantenuto le
[cose](https://www.corbado.com/blog/webauthn-pubkeycredparams-credentialpublickey) semplici e incentrate sulle
passkey.

**Come aggiungere le passkey al mio sito web di produzione?**

Questo è un esempio molto minimale di
[autenticazione](https://www.corbado.com/it/blog/come-passare-autenticazione-completamente-passwordless) con
passkey. I seguenti aspetti **NON** sono considerati/implementati in questo tutorial o lo
sono solo in modo molto basilare:

- UI condizionale / Mediazione condizionale / compilazione automatica delle passkey
- Gestione dei dispositivi
- Gestione delle sessioni
- Aggiunta sicura di più dispositivi a un account
- Compatibilità con le versioni precedenti
- Supporto adeguato multipiattaforma e multi-dispositivo
- [Autenticazione](https://www.corbado.com/it/blog/come-passare-autenticazione-completamente-passwordless) di
  fallback
- Gestione corretta degli errori
- Pagina di gestione delle passkey

Ottenere un supporto completo per tutte queste funzionalità richiede uno sforzo di
sviluppo enormemente maggiore. Per chi fosse interessato, consigliamo di dare un'occhiata
a questo articolo sui malintesi degli sviluppatori riguardo le passkey.

## 2. Prerequisiti per integrare le passkey

Prima di immergerci nell'implementazione delle passkey, diamo un'occhiata alle competenze
e agli strumenti necessari. Ecco cosa ti serve per iniziare:

### 2.1 Frontend: HTML e JavaScript Vanilla

Una solida conoscenza degli elementi costitutivi del web — HTML, CSS e JavaScript — è
essenziale. Abbiamo intenzionalmente mantenuto le
[cose](https://www.corbado.com/blog/webauthn-pubkeycredparams-credentialpublickey) semplici, astenendoci da
qualsiasi framework JavaScript moderno e affidandoci a JavaScript/HTML Vanilla. L'unica
cosa più sofisticata che usiamo è la libreria wrapper di WebAuthn
[@simplewebauthn/browser](https://simplewebauthn.dev/docs/packages/browser/).

### 2.2 Backend: Node.js (Express) in TypeScript + SimpleWebAuthn

Per il nostro backend, usiamo un server [Node.js](https://www.corbado.com/blog/nodejs-passkeys) (Express) scritto
in TypeScript. Abbiamo anche deciso di lavorare con l'implementazione del server WebAuthn
di SimpleWebAuthn (`@simplewebauthn/server` insieme a `@simplewebauthn/typescript-types`).
Esistono numerose implementazioni di server WebAuthn, quindi puoi ovviamente usare anche
una di queste. Poiché abbiamo optato per il server WebAuthn in TypeScript, sono richieste
conoscenze di base di [Node.js](https://www.corbado.com/blog/nodejs-passkeys) e npm.

### 2.3 Database: MySQL

Tutti i dati utente e le chiavi pubbliche delle passkey sono memorizzati in un database.
Abbiamo scelto [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) come tecnologia di database.
Una comprensione fondamentale di [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) e dei
database relazionali è vantaggiosa, anche se ti guideremo attraverso i singoli passaggi.

Di seguito, useremo spesso i termini WebAuthn e passkey in modo intercambiabile, anche se
ufficialmente potrebbero non significare la stessa cosa. Per una migliore comprensione,
specialmente nella parte del codice, facciamo comunque questa supposizione.

Con questi prerequisiti, sei pronto per immergerti nel mondo delle passkey.

## 3. Panoramica dell'architettura: esempio di implementazione delle passkey

Prima di entrare nel codice e nelle configurazioni, diamo un'occhiata all'architettura del
sistema che vogliamo costruire. Ecco una scomposizione dell'architettura che andremo a
configurare:

- **Frontend:** Consiste in due pulsanti, uno per la registrazione dell'utente (creazione
  di una passkey) e l'altro per l'autenticazione (accesso tramite la passkey).
- **Dispositivo e Browser:** Una volta che un'azione viene attivata sul frontend, entrano
  in gioco il dispositivo e il browser. Facilitano la creazione e la verifica della
  passkey, agendo da intermediari tra l'utente e il backend.
- **Backend:** Il backend è dove si svolge la vera magia nella nostra applicazione.
  Gestisce tutte le richieste avviate dal frontend. Questo processo include la creazione e
  la verifica delle passkey. Al centro delle operazioni del backend c'è il server
  WebAuthn. Contrariamente a quanto il nome potrebbe suggerire, non è un server autonomo.
  Invece, è una libreria o un pacchetto che implementa lo standard WebAuthn. Le due
  funzioni principali sono: **Registrazione (Sign-up) dove i nuovi utenti creano le loro
  passkey e Autenticazione (Login): dove gli utenti esistenti accedono usando le loro
  passkey.** Nella sua forma più semplice, il server WebAuthn fornisce quattro endpoint
  API pubblici, divisi in due categorie: due per la registrazione e due per
  l'autenticazione. Sono progettati per ricevere dati in un formato specifico, che viene
  poi elaborato dal server WebAuthn. Il server WebAuthn è responsabile di tutte le
  operazioni crittografiche necessarie. Un aspetto essenziale da notare è che questi
  endpoint API devono essere serviti tramite HTTPS.
- **Database MySQL:** Agendo come nostra spina dorsale di archiviazione, il database
  [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) è responsabile della conservazione dei
  dati utente e delle loro credenziali corrispondenti.

Con questa panoramica dell'architettura, dovresti avere una mappa concettuale di come
funzionano i componenti della nostra applicazione. Man mano che procediamo, approfondiremo
ciascuno di questi componenti, dettagliandone la configurazione, l'impostazione e
l'interazione.

Il seguente diagramma descrive il flusso del processo durante la registrazione (sign-up):

![Diagramma del processo di registrazione con passkey](https://www.corbado.com/website-assets/6572cd4d3243003bb3589e88_passkey_sign_up_process_chart_b73d643b4c.png)

Il seguente diagramma descrive il flusso del processo durante l'autenticazione (login):

![Diagramma del processo di accesso con passkey](https://www.corbado.com/website-assets/6572cd5f04fd73a7ff5d501b_passkey_login_process_chart_a44262b767.png)

Inoltre, qui trovi la struttura del progetto (solo i file più importanti):

```
passkeys-tutorial
├── src                         # Contiene tutto il codice sorgente TypeScript del backend
│   ├── controllers             # Logica di business per la gestione di tipi specifici di richieste
│   │   ├── authentication.ts   # Logica di autenticazione con passkey
│   │   └── registration.ts     # Logica di registrazione con passkey
│   ├── middleware
│   │   ├── customError.ts      # Aggiunge messaggi di errore personalizzati in modo standardizzato
│   │   └── errorHandler.ts     # Gestore di errori generale
│   ├── public
│   │   ├── index.html          # File HTML principale del frontend
│   │   ├── css
│   │   │   └── style.css       # Stile di base
│   │   └── js
│   │       └── script.js       # Logica JavaScript (incl. API WebAuthn)
│   ├── routes                  # Definizioni delle route API e dei loro gestori
│   │   └── routes.ts           # Route specifiche per le passkey
│   ├── services
│   │   ├── credentialService.ts# Interagisce con la tabella delle credenziali
│   │   └── userService.ts      # Interagisce con la tabella degli utenti
│   ├── utils                   # Funzioni di supporto e utilità
│   |   ├── constants.ts        # Alcune costanti (es. rpID)
│   |   └── utils.ts            # Funzione di supporto
│   ├── database.ts             # Crea la connessione da Node.js al database MySQL
│   ├── index.ts                # Punto di ingresso del server Node.js
│   └── server.ts               # Gestisce tutte le impostazioni del server
├── config.json                 # Alcune configurazioni per il progetto Node.js
├── docker-compose.yml          # Definisce servizi, reti e volumi per i container Docker
├── Dockerfile                  # Crea un'immagine Docker del progetto
├── init-db.sql                 # Definisce il nostro schema del database MySQL
├── package.json                # Gestisce le dipendenze e gli script del progetto Node.js
└── tsconfig.json               # Configura come TypeScript compila il tuo codice
```

## 4. Configurazione del database MySQL

Quando si implementano le passkey, la configurazione del database è un componente chiave.
Il nostro approccio utilizza un container Docker che esegue MySQL, offrendo un ambiente
semplice e isolato, essenziale per test e deployment affidabili.

Il nostro schema del database è intenzionalmente minimalista, con solo due tabelle. Questa
semplicità aiuta a una comprensione più chiara e a una manutenzione più facile.

**Struttura dettagliata delle tabelle**

**1. Tabella Credentials:** Centrale per l'autenticazione con passkey, questa tabella
memorizza le credenziali delle passkey. Colonne critiche:

- **credential_id:** Un identificatore univoco per ogni credenziale. La scelta del tipo di
  dati corretto per questo campo è vitale per evitare errori di formattazione.
- **public_key:** Memorizza la chiave pubblica per ogni credenziale. Come per
  `credential_id`, il tipo di dati e la formattazione appropriati sono cruciali.

**2. Tabella Users:** Collega gli account utente alle loro credenziali corrispondenti.

Nota che abbiamo chiamato la prima tabella `credentials` poiché, secondo la nostra
esperienza e ciò che altre librerie raccomandano, è più adatto (contrariamente al
suggerimento di SimpleWebAuthn di chiamarla `authenticator` o `authenticator_device`).

I tipi di dati per `credential_id` e `public_key` sono cruciali. Gli errori spesso
derivano da tipi di dati, codifica o formattazione errati (specialmente la differenza tra
Base64 e Base64URL è una causa comune di errori), che possono interrompere l'intero
processo di registrazione (sign-up) o autenticazione (login).

Tutti i comandi SQL necessari per la configurazione di queste tabelle sono contenuti nel
file `init-db.sql`. Questo script garantisce un'inizializzazione del database rapida e
senza errori.

Per casi più sofisticati, è possibile aggiungere `credential_device_type` o
`credential_backed_up` per memorizzare maggiori informazioni sulle credenziali e
migliorare l'esperienza utente. In questo tutorial, tuttavia, ci asteniamo dal farlo.

```sql filename="init-db.sql"
CREATE TABLE users
(
    id       VARCHAR(255) PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE credentials
(
    id            INT AUTO_INCREMENT PRIMARY KEY,
    user_id       VARCHAR(255) NOT NULL,
    credential_id VARCHAR(255) NOT NULL,
    public_key    TEXT         NOT NULL,
    counter       INT          NOT NULL,
    transports    VARCHAR(255),
    FOREIGN KEY (user_id) REFERENCES users (id)
);
```

Dopo aver creato questo file, creiamo un nuovo file `docker-compose.yml` a livello radice
del progetto:

```yaml filename="docker-compose.yml"
version: "3.1"

services:
    db:
        image: mysql
        command: --default-authentication-plugin=mysql_native_password
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: my-secret-pw
            MYSQL_DATABASE: webauthn_db
        ports:
            - "3306:3306"
        volumes:
            - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
```

Questo file avvia il database MySQL sulla porta 3306 e crea la struttura del database
definita. È importante notare che il nome e la password del database utilizzati qui sono
mantenuti semplici a scopo dimostrativo. In un ambiente di produzione, dovresti usare
credenziali più complesse per una maggiore
[sicurezza](https://www.corbado.com/it/blog/come-abilitare-passkey-su-android).

Successivamente, passiamo all'esecuzione del nostro container Docker. A questo punto, il
nostro file `docker-compose.yml` include solo questo singolo container, ma aggiungeremo
altri componenti in seguito. Per avviare il container, usa il seguente comando:

```bash
docker compose up -d
```

Una volta che il container è attivo e funzionante, dobbiamo verificare se il database
funziona come previsto. Apri un terminale ed esegui il seguente comando per interagire con
il database MySQL:

```bash
docker exec -it <container ID> mysql -uroot -p
```

Ti verrà richiesto di inserire la password di root, che nel nostro esempio è
`my-secret-pw`. Dopo aver effettuato l'accesso, seleziona il database `webauthn_db` e
visualizza le tabelle usando questi comandi:

```sql
use
webauthn_db;
show
tables;
```

A questo punto, dovresti vedere le due tabelle definite nel nostro script. Inizialmente,
queste tabelle saranno vuote, indicando che la nostra configurazione del database è
completa e pronta per i passaggi successivi nell'implementazione delle passkey.

## 5. Implementazione delle passkey: passaggi di integrazione del backend

Il backend è il cuore di qualsiasi applicazione con passkey, fungendo da hub centrale per
l'elaborazione delle richieste di autenticazione utente dal frontend. Comunica con la
libreria del server WebAuthn per gestire le richieste di registrazione (sign-up) e
autenticazione (login), e interagisce con il tuo database MySQL per memorizzare e
recuperare le credenziali utente. Di seguito, ti guideremo nella configurazione del tuo
backend usando [Node.js](https://www.corbado.com/blog/nodejs-passkeys) (Express) con TypeScript, che esporrà
un'API pubblica per gestire tutte le richieste.

### 5.1 Inizializzazione del server Node.js (Express)

Innanzitutto, crea una nuova directory per il tuo progetto e naviga al suo interno usando
il tuo terminale o prompt dei comandi.

Esegui il comando

```bash
npx create-express-typescript-application passkeys-tutorial
```

Questo crea uno scheletro di codice di base di un'app Node.js (Express) scritta in
TypeScript che possiamo usare per ulteriori adattamenti.

Il tuo progetto richiede diversi pacchetti chiave che dobbiamo installare in aggiunta:

- **@simplewebauthn/server:** Una libreria lato server per facilitare le operazioni
  WebAuthn, come la registrazione utente (sign-up) e l'autenticazione (login).
- **express-session:** Middleware per [Express.js](https://www.corbado.com/blog/nodejs-passkeys) per gestire le
  sessioni, memorizzando i dati di sessione lato server e gestendo i cookie.
- **uuid:** Un'utilità per generare identificatori univoci universali (UUID), comunemente
  usati per creare chiavi o identificatori univoci nelle applicazioni.
- **mysql2:** Un client Node.js per MySQL, che fornisce funzionalità per connettersi ed
  eseguire query su database MySQL.

Spostati nella nuova directory e installali con i seguenti comandi (installiamo anche i
tipi TypeScript richiesti):

```bash
cd passkeys-tutorial
npm install @simplewebauthn/server mysql2 uuid express-session
@types/express-session @types/uuid
```

Per confermare che tutto sia installato correttamente, esegui

```bash
npm run dev:nodemon
```

Questo dovrebbe avviare il tuo server Node.js in modalità di sviluppo con Nodemon, che
riavvia automaticamente il server a ogni modifica dei file.

![Avvio dell'app del tutorial sulle passkey](https://www.corbado.com/website-assets/6572cd86e1fa2982352c7ca1_passkey_tutorial_start_app_b442392b4d.png)

**Suggerimento per la risoluzione dei problemi:** Se incontri errori, prova ad aggiornare
`ts-node` alla versione 10.8.1 nel file `package.json` e poi esegui `npm i` per installare
gli aggiornamenti.

Il tuo file `server.ts` ha la configurazione di base e il middleware per un'applicazione
Express. Per integrare la funzionalità delle passkey, dovrai aggiungere:

- **Route:** Definisci nuove route per la registrazione (sign-up) e l'autenticazione
  (login) con passkey.
- **Controller:** Crea controller per gestire la logica di queste route.
- **Middleware:** Integra middleware per la gestione delle richieste e degli errori.
- **Servizi:** Costruisci servizi per recuperare e memorizzare dati nel database.
- **Funzioni di utilità:** Includi funzioni di utilità per operazioni di codice
  efficienti.

Questi miglioramenti sono fondamentali per abilitare l'autenticazione con passkey nel
backend della tua applicazione. Li configureremo più avanti.

### 5.2 Connessione al database MySQL

Dopo aver creato e avviato il database nella
[sezione 4](#4-configurazione-del-database-mysql), ora dobbiamo assicurarci che il nostro
backend possa connettersi al database MySQL. Pertanto, creiamo un nuovo file `database.ts`
nella cartella `/src` e aggiungiamo il seguente contenuto:

```ts filename="database.ts"
import mysql from "mysql2";

// Create a MySQL pool
const pool = mysql.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0,
});

// Promisify for Node.js async/await.
export const promisePool = pool.promise();
```

Questo file sarà successivamente utilizzato dal nostro server per accedere al database.

### 5.3 Configurazione del server dell'app

Diamo un'occhiata breve al nostro `config.json`, dove sono già definite due variabili: la
porta su cui eseguiamo l'applicazione e l'ambiente:

```json filename="config.json"
{
    "PORT": 8080,
    "NODE_ENV": "development"
}
```

`package.json` può rimanere così com'è e dovrebbe apparire così:

```json filename="package.json"
{
    "name": "passkeys-tutorial",
    "version": "0.0.1",
    "description": "passkeys-tutorial initialised with create-express-typescript-application.",
    "main": "src/index.ts",
    "scripts": {
        "build": "tsc",
        "start": "node ./build/src/index.js",
        "dev": "ts-node ./src/index.ts",
        "dev:nodemon": "nodemon -w src -e ts,json -x ts-node ./src/index.ts",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": ["express", "typescript"],
    "devDependencies": {
        "@types/cors": "^2.8.17",
        "@types/express": "^4.17.21",
        "@types/morgan": "^1.9.9",
        "@types/node": "^14.18.63",
        "@typescript-eslint/eslint-plugin": "^4.33.0",
        "@typescript-eslint/parser": "^4.33.0",
        "eslint": "^7.32.0",
        "nodemon": "^2.0.22",
        "ts-node": "^10.8.1",
        "typescript": "^4.9.5"
    },
    "dependencies": {
        "@simplewebauthn/server": "^8.3.5",
        "@types/express-session": "^1.17.10",
        "@types/uuid": "^9.0.7",
        "cors": "^2.8.5",
        "env-cmd": "^10.1.0",
        "express": "^4.18.2",
        "express-session": "^1.17.3",
        "fs": "^0.0.1-security",
        "helmet": "^4.6.0",
        "morgan": "^1.10.0",
        "mysql2": "^3.6.5",
        "uuid": "^9.0.1"
    }
}
```

`index.ts` appare così:

```ts filename="index.ts"
import app from "./server";
import config from "../config.json";

// Start the application by listening to specific port
const port = Number(process.env.PORT || config.PORT || 8080);
app.listen(port, () => {
    console.info("Express application started on port: " + port);
});
```

In `server.ts`, dobbiamo adattare alcune altre cose. Inoltre, è necessaria una cache
temporanea di qualche tipo (ad es. redis, memcache o express-session) per memorizzare le
challenge temporanee contro cui gli utenti possono autenticarsi. Abbiamo deciso di usare
`express-session` e dichiariamo il modulo `express-session` in cima per far funzionare le
cose con `express-session`. Inoltre, semplifichiamo il routing e rimuoviamo per ora la
gestione degli errori (questa sarà aggiunta al middleware in seguito):

```ts filename="server.ts"
import express, { Express } from "express";
import morgan from "morgan";
import helmet from "helmet";
import cors from "cors";
import config from "../config.json";
import { router as passkeyRoutes } from "./routes/routes";
import session from "express-session";

const app: Express = express();

declare module "express-session" {
    interface SessionData {
        currentChallenge?: string;
        loggedInUserId?: string;
    }
}

/************************************************************************************
 *                              Basic Express Middlewares
 ***********************************************************************************/
app.set("json spaces", 4);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(
    session({
        // @ts-ignore
        secret: process.env.SESSION_SECRET,
        saveUninitialized: true,
        resave: false,
        cookie: {
            maxAge: 86400000,
            httpOnly: true, // Ensure to not expose session cookies to clientside scripts
        },
    }),
);

// Handle logs in console during development
if (process.env.NODE_ENV === "development" || config.NODE_ENV === "development") {
    app.use(morgan("dev"));
    app.use(cors());
}

// Handle security and origin in production
if (process.env.NODE_ENV === "production" || config.NODE_ENV === "production") {
    app.use(helmet());
}

/************************************************************************************
 *                               Register all routes
 ***********************************************************************************/
app.use("/api/passkey", passkeyRoutes);

app.use(express.static("src/public"));

export default app;
```

### 5.4 Servizio credenziali e servizio utenti

Per gestire efficacemente i dati nelle nostre due tabelle create, svilupperemo due servizi
distinti in una nuova directory `src/services`: `authenticatorService.ts` e
`userService.ts`.

Ogni servizio incapsulerà i metodi CRUD (Create, Read, Update, Delete), consentendoci di
interagire con il database in modo modulare e organizzato. Questi servizi faciliteranno la
memorizzazione, il recupero e l'aggiornamento dei dati nelle tabelle `authenticator` e
`user`. Ecco come dovrebbe essere la struttura di questi file richiesti:

`userService.ts` appare così:

```ts filename="userService.ts"
import { promisePool } from "../database"; // Adjust the import path as necessary
import { v4 as uuidv4 } from "uuid";

export const userService = {
    async getUserById(userId: string) {
        const [rows] = await promisePool.query("SELECT * FROM users WHERE id = ?", [
            userId,
        ]);
        // @ts-ignore
        return rows[0];
    },

    async getUserByUsername(username: string) {
        try {
            const [rows] = await promisePool.query(
                "SELECT * FROM users WHERE username = ?",
                [username],
            );
            // @ts-ignore
            return rows[0];
        } catch (error) {
            return null;
        }
    },

    async createUser(username: string) {
        const id = uuidv4();
        await promisePool.query("INSERT INTO users (id, username) VALUES (?, ?)", [
            id,
            username,
        ]);
        return { id, username };
    },
};
```

`credentialService.ts` appare come segue:

```ts filename="credentialService.ts"
import { promisePool } from "../database";
import type { AuthenticatorDevice } from "@simplewebauthn/typescript-types";

export const credentialService = {
    async saveNewCredential(
        userId: string,
        credentialId: string,
        publicKey: string,
        counter: number,
        transports: string,
    ) {
        try {
            await promisePool.query(
                "INSERT INTO credentials (user_id, credential_id, public_key, counter, transports) VALUES (?, ?, ?, ?, ?)",
                [userId, credentialId, publicKey, counter, transports],
            );
        } catch (error) {
            console.error("Error saving new credential:", error);
            throw error;
        }
    },

    async getCredentialByCredentialId(
        credentialId: string,
    ): Promise<AuthenticatorDevice | null> {
        try {
            const [rows] = await promisePool.query(
                "SELECT * FROM credentials WHERE credential_id = ? LIMIT 1",
                [credentialId],
            );
            // @ts-ignore
            if (rows.length === 0) return null;
            // @ts-ignore
            const row = rows[0];
            return {
                userID: row.user_id,
                credentialID: row.credential_id,
                credentialPublicKey: row.public_key,
                counter: row.counter,
                transports: row.transports ? row.transports.split(",") : [],
            } as AuthenticatorDevice;
        } catch (error) {
            console.error("Error retrieving credential:", error);
            throw error;
        }
    },

    async updateCredentialCounter(credentialId: string, newCounter: number) {
        try {
            await promisePool.query(
                "UPDATE credentials SET counter = ? WHERE credential_id = ?",
                [newCounter, credentialId],
            );
        } catch (error) {
            console.error("Error updating credential counter:", error);
            throw error;
        }
    },
};
```

### 5.5 Middleware

Per gestire gli errori centralmente e anche per facilitare il debug, aggiungiamo un file
`errorHandler.ts`:

```ts filename="errorHandler.ts"
import { Request, Response, NextFunction } from "express";
import { CustomError } from "./customError";

interface ErrorWithStatus extends Error {
    statusCode?: number;
}

export const handleError = (
    err: CustomError,
    req: Request,
    res: Response,
    next: NextFunction,
) => {
    const statusCode = err.statusCode || 500;
    const message = err.message || "Internal Server Error";
    console.log(message);
    res.status(statusCode).send({ error: message });
};
```

Inoltre, aggiungiamo un nuovo file `customError.ts` poiché in seguito vorremo essere in
grado di creare errori personalizzati per aiutarci a trovare i bug più rapidamente:

```ts filename="customError.ts"
export class CustomError extends Error {
    statusCode: number;

    constructor(message: string, statusCode: number = 500) {
        super(message);
        this.statusCode = statusCode;
        Object.setPrototypeOf(this, CustomError.prototype);
    }
}
```

### 5.6 Utilità

Nella cartella `utils`, creiamo due file `constants.ts` e `utils.ts`.

`constant.ts` contiene alcune informazioni di base del server WebAuthn, come il nome della
[relying party](https://www.corbado.com/glossary/relying-party), l'ID della
[relying party](https://www.corbado.com/glossary/relying-party) e l'origine:

```ts filename="constant.ts"
export const rpName: string = "Passkeys Tutorial";
export const rpID: string = "localhost";
export const origin: string = `http://${rpID}:8080`;
```

`utils.ts` contiene due funzioni che ci serviranno in seguito per la codifica e la
decodifica dei dati:

```ts filename="utils.ts"
export const uint8ArrayToBase64 = (uint8Array: Uint8Array): string =>
    Buffer.from(uint8Array).toString("base64");

export const base64ToUint8Array = (base64: string): Uint8Array =>
    new Uint8Array(Buffer.from(base64, "base64"));
```

### 5.7 Controller delle passkey con SimpleWebAuthn

Ora, arriviamo al cuore del nostro backend: i controller. Creiamo due controller, uno per
la creazione di una nuova passkey (`registration.ts`) e uno per l'accesso con una passkey
(`authentication.ts`).

`registration.ts` appare così:

```ts filename="registration.ts"
import {
    generateRegistrationOptions,
    verifyRegistrationResponse,
} from "@simplewebauthn/server";
import { uint8ArrayToBase64 } from "../utils/utils";
import { rpName, rpID, origin } from "../utils/constants";
import { credentialService } from "../services/credentialService";
import { userService } from "../services/userService";
import { RegistrationResponseJSON } from "@simplewebauthn/typescript-types";
import { Request, Response, NextFunction } from "express";
import { CustomError } from "../middleware/customError";

export const handleRegisterStart = async (
    req: Request,
    res: Response,
    next: NextFunction,
) => {
    const { username } = req.body;

    if (!username) {
        return next(new CustomError("Username empty", 400));
    }

    try {
        let user = await userService.getUserByUsername(username);
        if (user) {
            return next(new CustomError("User already exists", 400));
        } else {
            user = await userService.createUser(username);
        }

        const options = await generateRegistrationOptions({
            rpName,
            rpID,
            userID: user.id,
            userName: user.username,
            timeout: 60000,
            attestationType: "direct",
            excludeCredentials: [],
            authenticatorSelection: {
                residentKey: "preferred",
            },
            // Support for the two most common algorithms: ES256, and RS256
            supportedAlgorithmIDs: [-7, -257],
        });
        req.session.loggedInUserId = user.id;
        req.session.currentChallenge = options.challenge;
        res.send(options);
    } catch (error) {
        next(
            error instanceof CustomError
                ? error
                : new CustomError("Internal Server Error", 500),
        );
    }
};

export const handleRegisterFinish = async (
    req: Request,
    res: Response,
    next: NextFunction,
) => {
    const { body } = req;
    const { currentChallenge, loggedInUserId } = req.session;

    if (!loggedInUserId) {
        return next(new CustomError("User ID is missing", 400));
    }

    if (!currentChallenge) {
        return next(new CustomError("Current challenge is missing", 400));
    }

    try {
        const verification = await verifyRegistrationResponse({
            response: body as RegistrationResponseJSON,
            expectedChallenge: currentChallenge,
            expectedOrigin: origin,
            expectedRPID: rpID,
            requireUserVerification: true,
        });

        if (verification.verified && verification.registrationInfo) {
            const { credentialPublicKey, credentialID, counter } =
                verification.registrationInfo;
            await credentialService.saveNewCredential(
                loggedInUserId,
                uint8ArrayToBase64(credentialID),
                uint8ArrayToBase64(credentialPublicKey),
                counter,
                body.response.transports,
            );
            res.send({ verified: true });
        } else {
            next(new CustomError("Verification failed", 400));
        }
    } catch (error) {
        next(
            error instanceof CustomError
                ? error
                : new CustomError("Internal Server Error", 500),
        );
    } finally {
        req.session.loggedInUserId = undefined;
        req.session.currentChallenge = undefined;
    }
};
```

Esaminiamo le funzionalità dei nostri controller, che gestiscono i due endpoint chiave nel
processo di registrazione (sign-up) di WebAuthn. È qui che si trova una delle maggiori
differenze rispetto all'autenticazione basata su password: per ogni tentativo di
registrazione (sign-up) o autenticazione (login), sono necessarie due chiamate API al
backend, che richiedono un contenuto specifico del frontend nel mezzo. Le password di
solito necessitano di un solo endpoint.

**1. Endpoint handleRegisterStart:**

Questo endpoint viene attivato dal frontend, ricevendo un nome utente per creare una nuova
passkey e un nuovo account. In questo esempio, consentiamo la creazione di un nuovo
account/passkey solo se non esiste ancora un account. Nelle applicazioni reali, dovresti
gestire questo in modo che agli utenti venga detto che una passkey esiste già e che
l'aggiunta dallo stesso dispositivo non è possibile (ma l'utente potrebbe aggiungere
passkey da un dispositivo diverso dopo una qualche forma di conferma). Per semplicità, in
questo tutorial trascuriamo questo aspetto.

Le `PublicKeyCredentialCreationOptions` vengono preparate. `residentKey` è impostato su
`preferred` e `attestationType` su `direct`, raccogliendo più dati
dall'[authenticator](https://www.corbado.com/glossary/authenticator) per una potenziale memorizzazione nel
database.

In generale, le `PublicKeyCredentialCreationOptions` sono composte dai seguenti dati:

```
dictionary PublicKeyCredentialCreationOptions {
    required PublicKeyCredentialRpEntity                rp;
    required PublicKeyCredentialUserEntity              user;

    required BufferSource                               challenge;
    required sequence<PublicKeyCredentialParameters>    pubKeyCredParams;

    unsigned long                                       timeout;
    sequence<PublicKeyCredentialDescriptor>             excludeCredentials = [];
    AuthenticatorSelectionCriteria                      authenticatorSelection;
    DOMString                                           attestation = "none";
    AuthenticationExtensionsClientInputs                extensions;
};
```

- **rp:** Rappresenta le informazioni della [relying party](https://www.corbado.com/glossary/relying-party) (sito
  web o servizio), includendo tipicamente il suo nome (`rp.name`) e il dominio (`rp.id`).
- **user:** Contiene i dettagli dell'account utente come `user.name`, `user.id` e
  `user.displayName`.
- **challenge:** Un valore casuale e sicuro creato dal server WebAuthn per prevenire
  attacchi di replay durante il processo di registrazione.
- **pubKeyCredParams:** Specifica il tipo di credenziale a chiave pubblica da creare,
  incluso l'algoritmo crittografico utilizzato.
- **timeout:** Opzionale, imposta il tempo in millisecondi che l'utente ha per completare
  l'interazione.
- **excludeCredentials:** Un elenco di credenziali da escludere; utilizzato per impedire
  la registrazione di una passkey per lo stesso
  dispositivo/[authenticator](https://www.corbado.com/glossary/authenticator) più volte.
- **authenticatorSelection:** Criteri per selezionare
  l'[authenticator](https://www.corbado.com/glossary/authenticator), ad esempio se deve supportare la verifica
  dell'utente o come incoraggiare le chiavi residenti.
- **attestation:** Specifica la preferenza di trasmissione
  dell'[attestazione](https://www.corbado.com/it/glossary/attestation) desiderata, come "none", "indirect" o
  "direct".
- **extensions:** Opzionale, consente estensioni client aggiuntive.

L'ID utente e la challenge vengono memorizzati in un oggetto di sessione, semplificando il
processo a scopo di tutorial. Inoltre, la sessione viene cancellata dopo ogni tentativo di
registrazione (sign-up) o autenticazione (login).

**2. Endpoint handleRegisterFinish:**

Questo endpoint recupera l'ID utente e la challenge impostati in precedenza. Verifica la
`RegistrationResponse` con la challenge. Se valida, memorizza una nuova credenziale per
l'utente. Una volta memorizzati nel database, l'ID utente e la challenge vengono rimossi
dalla sessione.

Suggerimento: durante il debug della tua applicazione, consigliamo vivamente di utilizzare
Chrome come browser e le sue funzionalità integrate per migliorare l'esperienza di
sviluppo di applicazioni basate su passkey, ad esempio l'authenticator WebAuthn virtuale e
il log del dispositivo (vedi i nostri consigli per gli sviluppatori di seguito per
maggiori informazioni).

Successivamente, passiamo a `authentication.ts`, che ha una struttura e una funzionalità
simili.

`authentication.ts` appare così:

```ts filename="authentication.ts"
import { Request, Response, NextFunction } from "express";
import {
    generateAuthenticationOptions,
    verifyAuthenticationResponse,
} from "@simplewebauthn/server";
import { uint8ArrayToBase64, base64ToUint8Array } from "../utils/utils";
import { rpID, origin } from "../utils/constants";
import { credentialService } from "../services/credentialService";
import { userService } from "../services/userService";
import { AuthenticatorDevice } from "@simplewebauthn/typescript-types";
import { isoBase64URL } from "@simplewebauthn/server/helpers";
import {
    VerifiedAuthenticationResponse,
    VerifyAuthenticationResponseOpts,
} from "@simplewebauthn/server/esm";
import { CustomError } from "../middleware/customError";

export const handleLoginStart = async (
    req: Request,
    res: Response,
    next: NextFunction,
) => {
    const { username } = req.body;
    try {
        const user = await userService.getUserByUsername(username);
        if (!user) {
            return next(new CustomError("User not found", 404));
        }

        req.session.loggedInUserId = user.id;

        // allowCredentials is purposely for this demo left empty. This causes all existing local credentials
        // to be displayed for the service instead only the ones the username has registered.
        const options = await generateAuthenticationOptions({
            timeout: 60000,
            allowCredentials: [],
            userVerification: "required",
            rpID,
        });

        req.session.currentChallenge = options.challenge;
        res.send(options);
    } catch (error) {
        next(
            error instanceof CustomError
                ? error
                : new CustomError("Internal Server Error", 500),
        );
    }
};

export const handleLoginFinish = async (
    req: Request,
    res: Response,
    next: NextFunction,
) => {
    const { body } = req;
    const { currentChallenge, loggedInUserId } = req.session;

    if (!loggedInUserId) {
        return next(new CustomError("User ID is missing", 400));
    }

    if (!currentChallenge) {
        return next(new CustomError("Current challenge is missing", 400));
    }

    try {
        const credentialID = isoBase64URL.toBase64(body.rawId);
        const bodyCredIDBuffer = isoBase64URL.toBuffer(body.rawId);
        const dbCredential: AuthenticatorDevice | null =
            await credentialService.getCredentialByCredentialId(credentialID);
        if (!dbCredential) {
            return next(new CustomError("Credential not registered with this site", 404));
        }

        // @ts-ignore
        const user = await userService.getUserById(dbCredential.userID);
        if (!user) {
            return next(new CustomError("User not found", 404));
        }

        // @ts-ignore
        dbCredential.credentialID = base64ToUint8Array(dbCredential.credentialID);
        // @ts-ignore
        dbCredential.credentialPublicKey = base64ToUint8Array(
            dbCredential.credentialPublicKey,
        );

        let verification: VerifiedAuthenticationResponse;
        const opts: VerifyAuthenticationResponseOpts = {
            response: body,
            expectedChallenge: currentChallenge,
            expectedOrigin: origin,
            expectedRPID: rpID,
            authenticator: dbCredential,
        };
        verification = await verifyAuthenticationResponse(opts);
        const { verified, authenticationInfo } = verification;

        if (verified) {
            await credentialService.updateCredentialCounter(
                uint8ArrayToBase64(bodyCredIDBuffer),
                authenticationInfo.newCounter,
            );
            res.send({ verified: true });
        } else {
            next(new CustomError("Verification failed", 400));
        }
    } catch (error) {
        next(
            error instanceof CustomError
                ? error
                : new CustomError("Internal Server Error", 500),
        );
    } finally {
        req.session.currentChallenge = undefined;
        req.session.loggedInUserId = undefined;
    }
};
```

Il nostro processo di autenticazione (login) coinvolge due endpoint:

**1. Endpoint handleLoginStart:**

Questo endpoint viene attivato quando un utente tenta di accedere. Prima controlla se il
nome utente esiste nel database, restituendo un errore se non viene trovato. In uno
scenario reale, potresti offrire di creare un nuovo account.

Per gli utenti esistenti, recupera l'ID utente dal database, lo memorizza nella sessione e
genera le opzioni `PublicKeyCredentialRequestOptions`. `allowCredentials` viene lasciato
vuoto per non limitare l'uso delle credenziali. Ecco perché tutte le passkey disponibili
per questa relying party possono essere selezionate nella finestra modale delle passkey.

La challenge generata viene anche memorizzata nella sessione e le
`PublicKeyCredentialRequestOptions` vengono inviate al frontend.

Le `PublicKeyCredentialRequestOptions` sono composte dai seguenti dati:

```
dictionary PublicKeyCredentialRequestOptions {
    required BufferSource                challenge;
    unsigned long                        timeout;
    USVString                            rpId;
    sequence<PublicKeyCredentialDescriptor> allowCredentials = [];
    DOMString                            userVerification = "preferred";
    AuthenticationExtensionsClientInputs extensions;
};
```

- **challenge:** Un valore casuale e sicuro dal server WebAuthn utilizzato per prevenire
  attacchi di replay durante il processo di autenticazione.
- **timeout:** Opzionale, imposta il tempo in millisecondi che l'utente ha per rispondere
  alla richiesta di autenticazione.
- **rpId:** L'ID della relying party, tipicamente il dominio del servizio.
- **allowCredentials:** Un elenco opzionale di descrittori di credenziali, che specifica
  quali credenziali possono essere utilizzate per questa autenticazione (login).
- **userVerification:** Specifica il requisito per la verifica dell'utente, come
  "required", "preferred" o "discouraged".
- **extensions:** Opzionale, consente estensioni client aggiuntive.

**2. Endpoint handleLoginFinish:**

Questo endpoint recupera `currentChallenge` e `loggedInUserId` dalla sessione.

Interroga il database per la credenziale corretta utilizzando l'ID della credenziale dal
corpo della richiesta. Se la credenziale viene trovata, significa che l'utente associato a
questo ID di credenziale può ora essere autenticato (loggato). Quindi, possiamo
interrogare l'utente dalla tabella degli utenti tramite l'ID utente che otteniamo dalla
credenziale e verificare la `authenticationResponse` utilizzando la challenge e il corpo
della richiesta. Se tutto ha successo, mostriamo il messaggio di successo del login. Se
non viene trovata alcuna credenziale corrispondente, viene inviato un errore.

Inoltre, se la verifica ha successo, il contatore della credenziale viene aggiornato, la
challenge utilizzata e il loggedInUserId vengono rimossi dalla sessione.

Inoltre, possiamo eliminare le cartelle `src/app` e `src/constant` insieme a tutti i file
al loro interno.

Nota: la gestione corretta delle sessioni e la protezione delle route, cruciali nelle
applicazioni reali, sono omesse qui per semplicità in questo tutorial.

### 5.8 Route delle passkey

Infine, ma non meno importante, dobbiamo assicurarci che i nostri controller siano
raggiungibili aggiungendo le route appropriate a `routes.ts` che si trova in una nuova
directory `src/routes`:

```ts filename="routes.ts"
import express from "express";
import { handleError } from "../middleware/errorHandler";
import { handleRegisterStart, handleRegisterFinish } from "../controllers/registration";
import { handleLoginStart, handleLoginFinish } from "../controllers/authentication";

const router = express.Router();

router.post("/registerStart", handleRegisterStart);
router.post("/registerFinish", handleRegisterFinish);
router.post("/loginStart", handleLoginStart);
router.post("/loginFinish", handleLoginFinish);

router.use(handleError);

export { router };
```

## 6. Integrare le passkey nel frontend

Questa parte del tutorial sulle passkey si concentra su come supportare le passkey nel
frontend della tua applicazione. Abbiamo un frontend molto basilare composto da tre file:
`index.html`, `styles.css` e `script.js`. Tutti e tre i file si trovano in una nuova
cartella `src/public`.

Il file `index.html` contiene un campo di input per il nome utente e due pulsanti per
registrarsi e accedere. Inoltre, importiamo lo script `@simplewebauthn/browser` che
semplifica l'interazione con l'API di autenticazione web del browser nel file
`js/script.js`.

`index.html` appare così:

```html filename="index.html"
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Passkey Tutorial</title>
        <link rel="stylesheet" href="css/style.css" />
    </head>
    <body>
        <div class="container">
            <h1>Passkey Tutorial</h1>
            <div id="message"></div>

            <div class="input-group">
                <input type="text" id="username" placeholder="Enter username" />
                <button id="registerButton">Register</button>
                <button id="loginButton">Login</button>
            </div>
        </div>

        <script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.es5.umd.min.js"></script>
        <script src="js/script.js"></script>
    </body>
</html>
```

`script.js` appare come segue:

```javascript filename="script.js"
document.getElementById("registerButton").addEventListener("click", register);
document.getElementById("loginButton").addEventListener("click", login);

function showMessage(message, isError = false) {
    const messageElement = document.getElementById("message");
    messageElement.textContent = message;
    messageElement.style.color = isError ? "red" : "green";
}

async function register() {
    // Retrieve the username from the input field
    const username = document.getElementById("username").value;

    try {
        // Get registration options from your server. Here, we also receive the challenge.
        const response = await fetch("/api/passkey/registerStart", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ username: username }),
        });
        console.log(response);

        // Check if the registration options are ok.
        if (!response.ok) {
            throw new Error(
                "User already exists or failed to get registration options from server",
            );
        }

        // Convert the registration options to JSON.
        const options = await response.json();
        console.log(options);

        // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
        // A new attestation is created. This also means a new public-private-key pair is created.
        const attestationResponse =
            await SimpleWebAuthnBrowser.startRegistration(options);

        // Send attestationResponse back to server for verification and storage.
        const verificationResponse = await fetch("/api/passkey/registerFinish", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(attestationResponse),
        });

        if (verificationResponse.ok) {
            showMessage("Registration successful");
        } else {
            showMessage("Registration failed", true);
        }
    } catch (error) {
        showMessage("Error: " + error.message, true);
    }
}

async function login() {
    // Retrieve the username from the input field
    const username = document.getElementById("username").value;

    try {
        // Get login options from your server. Here, we also receive the challenge.
        const response = await fetch("/api/passkey/loginStart", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ username: username }),
        });
        // Check if the login options are ok.
        if (!response.ok) {
            throw new Error("Failed to get login options from server");
        }
        // Convert the login options to JSON.
        const options = await response.json();
        console.log(options);

        // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
        // A new assertionResponse is created. This also means that the challenge has been signed.
        const assertionResponse =
            await SimpleWebAuthnBrowser.startAuthentication(options);

        // Send assertionResponse back to server for verification.
        const verificationResponse = await fetch("/api/passkey/loginFinish", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(assertionResponse),
        });

        if (verificationResponse.ok) {
            showMessage("Login successful");
        } else {
            showMessage("Login failed", true);
        }
    } catch (error) {
        showMessage("Error: " + error.message, true);
    }
}
```

In `script.js`, ci sono tre funzioni principali:

**1. Funzione showMessage:**

Questa è una funzione di utilità utilizzata principalmente per visualizzare messaggi di
errore, aiutando nel debug.

**2. Funzione Register:**

Attivata quando l'utente fa clic su "Register". Estrae il nome utente dal campo di input e
lo invia all'endpoint `passkeyRegisterStart`. La risposta include
**`PublicKeyCredentialCreationOptions`**, che vengono convertite in JSON e passate a
`SimpleWebAuthnBrowser.startRegistration`. Questa chiamata attiva l'authenticator del
dispositivo (come [Face ID](https://www.corbado.com/faq/is-face-id-passkey) o Touch ID). Dopo un'autenticazione
locale riuscita, la challenge firmata viene inviata all'endpoint `passkeyRegisterFinish`,
completando il processo di creazione della passkey.

Durante il processo di registrazione (sign-up), l'oggetto di
[attestazione](https://www.corbado.com/it/glossary/attestation) gioca un ruolo cruciale, quindi diamogli
un'occhiata più da vicino.

![Oggetto di attestazione della passkey](https://www.corbado.com/website-assets/6572cda3f3869adee2c38e05_passkey_attestation_object_5bfcce1977.png)

L'oggetto di [attestazione](https://www.corbado.com/it/glossary/attestation) è composto principalmente da tre
componenti: `fmt`, `attStmt` e `authData`. L'elemento `fmt` indica il formato della
dichiarazione di attestazione, mentre `attStmt` rappresenta la dichiarazione di
attestazione stessa. In scenari in cui l'attestazione non è ritenuta necessaria, `fmt`
sarà designato come "none", portando a un `attStmt` vuoto.

L'attenzione si concentra sul segmento `authData` all'interno di questa struttura. Questo
segmento è fondamentale per recuperare elementi essenziali come l'ID della relying party,
i flag, il contatore e i dati della credenziale attestata sul nostro server. Per quanto
riguarda i flag, di particolare interesse sono BS (Backup State) e BE (Backup Eligibility)
che forniscono maggiori informazioni se una passkey è sincronizzata (ad esempio tramite
[iCloud Keychain](https://www.corbado.com/glossary/icloud-keychain) o
[1Password](https://www.corbado.com/blog/1password-passkeys-best-practices-analysis)). Inoltre, UV (User
Verification) e UP (User Presence) forniscono informazioni più utili.

![Dati dell'authenticator della passkey](https://www.corbado.com/website-assets/6572cf3ce1fa2982352d6c88_passkey_authenticator_data_7f61aceaea.png)

È importante notare che varie parti dell'oggetto di attestazione, inclusi i dati
dell'authenticator, l'ID della relying party e la dichiarazione di attestazione, sono o
sottoposte a hash o firmate digitalmente dall'authenticator utilizzando la sua chiave
privata. Questo processo è fondamentale per mantenere l'integrità complessiva dell'oggetto
di attestazione.

**3. Funzione Login:**

Attivata quando l'utente fa clic su "Login". Similmente alla funzione di registrazione,
estrae il nome utente e lo invia all'endpoint `passkeyLoginStart`. La risposta, contenente
**`PublicKeyCredentialRequestOptions`**, viene convertita in JSON e utilizzata con
`SimpleWebAuthnBrowser.startAuthentication`. Questo attiva l'autenticazione locale sul
dispositivo. La challenge firmata viene quindi inviata all'endpoint `passkeyLoginFinish`.
Una risposta positiva da questo endpoint indica che l'utente ha effettuato l'accesso
all'app con successo.

Inoltre, il file CSS di accompagnamento fornisce uno stile semplice per l'applicazione:

```css
body {
    font-family: "Helvetica Neue", Arial, sans-serif;
    text-align: center;
    padding: 40px;
    background-color: #f3f4f6;
    color: #333;
}

.container {
    max-width: 400px;
    margin: auto;
    background: white;
    padding: 20px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    border-radius: 8px;
}

h1 {
    color: #007bff;
    font-size: 24px;
    margin-bottom: 20px;
}

.input-group {
    margin-bottom: 20px;
}

input[type="text"] {
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ced4da;
    border-radius: 4px;
    width: calc(100% - 22px);
}

button {
    width: calc(50% - 20px);
    padding: 10px 0;
    margin: 5px;
    font-size: 16px;
    cursor: pointer;
    border: none;
    border-radius: 4px;
    background-color: #007bff;
    color: white;
}

button:hover {
    background-color: #0056b3;
}

#message {
    color: #dc3545;
    margin: 20px;
}
```

## 7. Eseguire l'app di esempio con le passkey

Per vedere la tua applicazione in azione, compila ed esegui il tuo codice TypeScript con:

```bash
npm run dev
```

Il tuo server dovrebbe ora essere attivo e funzionante su
[http://localhost:8080](http://localhost:8080).

**Considerazioni per la produzione:**

Ricorda, ciò che abbiamo trattato è uno schema di base. Quando si distribuisce
un'applicazione con passkey in un ambiente di produzione, è necessario approfondire:

- **Misure di sicurezza:** Implementa pratiche di
  [sicurezza](https://www.corbado.com/it/blog/come-abilitare-passkey-su-android) robuste per proteggere i dati
  degli utenti.
- **Gestione degli errori:** Assicurati che la tua applicazione gestisca e registri gli
  errori in modo appropriato.
- **Gestione del database:** Ottimizza le operazioni del database per la scalabilità e
  l'affidabilità.

## 8. Integrazione DevOps per le passkey

Abbiamo già configurato un container Docker per il nostro database. Successivamente,
espanderemo la nostra configurazione di Docker Compose per includere il server con sia il
backend che il frontend. Il tuo file `docker-compose.yml` dovrebbe essere aggiornato di
conseguenza.

Per containerizzare la nostra applicazione, creiamo un nuovo Dockerfile che installa i
pacchetti richiesti e avvia il server di sviluppo:

```docker filename="Docker"
# Use an official Node runtime as a parent image
FROM node:20-alpine

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install any needed packages
RUN npm install

# Bundle your app's source code inside the Docker image
COPY . .

# Make port 8080 available to the world outside this container
EXPOSE 8080

# Define the command to run your app
CMD ["npm", "run", "dev"]
```

Quindi, estendiamo anche il file `docker-compose.yml` per avviare questo container:

```yaml filename="docker-compose.yml"
version: "3.1"

services:
    db:
        image: mysql
        command: --default-authentication-plugin=mysql_native_password
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: my-secret-pw
            MYSQL_DATABASE: webauthn_db
        ports:
            - "3306:3306"
        volumes:
            - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql

    app:
        build: .
        ports:
            - "8080:8080"
        environment:
            - DB_HOST=db
            - DB_USER=root
            - DB_PASSWORD=my-secret-pw
            - DB_NAME=webauthn_db
            - SESSION_SECRET=secret123
        depends_on:
            - db
```

Se ora esegui `docker compose up` nel tuo terminale e accedi a
[http://localhost:8080](http://localhost:8080), dovresti vedere la versione funzionante
della tua app web con passkey (qui in esecuzione su
[Windows 11](https://www.corbado.com/blog/passkeys-windows-11) 23H2 + Chrome 119):

![Tutorial passkey con Windows Hello](https://www.corbado.com/website-assets/6572cf57f3869adee2c49ff5_passkey_tutorial_windows_hello_5ac0585957.png)

## 9. Consigli aggiuntivi sulle passkey per gli sviluppatori

Poiché lavoriamo da un po' di tempo con le implementazioni di passkey, abbiamo incontrato
un paio di sfide quando si lavora su app con passkey reali:

- Compatibilità e supporto di dispositivi/piattaforme
- Onboarding ed educazione degli utenti
- Gestione di dispositivi persi o cambiati
- Autenticazione multipiattaforma
- Meccanismi di fallback
- Complessità della codifica: la codifica è spesso la parte più difficile poiché si ha a
  che fare con JSON, [CBOR](https://www.corbado.com/glossary/cbor), uint8array, buffer, blob, diversi database,
  base64 e base64url, dove possono verificarsi molti errori
- Gestione delle passkey (ad es. per aggiungere, eliminare o rinominare le passkey)

Inoltre, abbiamo i seguenti suggerimenti per gli sviluppatori per quanto riguarda la parte
di implementazione:

**Utilizza il Passkeys Debugger**

Il [**Passkeys debugger**](https://www.passkeys-debugger.io/) aiuta a testare diverse
impostazioni del server WebAuthn e le risposte del client. Inoltre, fornisce un ottimo
parser per le risposte dell'authenticator.

**Esegui il debug con la funzione Device Log di Chrome**

Usa il log del dispositivo di Chrome (accessibile tramite
[chrome://device-log/](chrome://device-log/)) per monitorare le chiamate FIDO/WebAuthn.
Questa funzione fornisce log in tempo reale del processo di autenticazione (login),
consentendoti di vedere i dati scambiati e risolvere eventuali problemi che si presentano.

Un'altra scorciatoia molto utile per ottenere tutte le tue passkey in Chrome è usare
[chrome://settings/passkeys](chrome://settings/passkeys).

**Usa l'Authenticator WebAuthn virtuale di Chrome**

Per evitare di utilizzare il prompt di Touch ID, [Face ID](https://www.corbado.com/faq/is-face-id-passkey) o
[Windows Hello](https://www.corbado.com/glossary/windows-hello) durante lo sviluppo, Chrome è dotato di un
pratico authenticator WebAuthn virtuale che emula un vero authenticator. Consigliamo
vivamente di usarlo per accelerare le cose. Trova maggiori dettagli
[qui](https://developer.chrome.com/docs/devtools/webauthn/).

**Testa su diverse piattaforme e browser**

Assicurati la compatibilità e la funzionalità su vari browser e piattaforme. WebAuthn si
comporta in modo diverso su browser diversi, quindi un test approfondito è fondamentale.

**Testa su dispositivi diversi**

Qui è particolarmente utile lavorare con strumenti come
[ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok), dove puoi rendere la tua
applicazione locale raggiungibile su altri dispositivi (mobili).

**Imposta la verifica dell'utente su `preferred`**

Quando si definiscono le proprietà per `userVerification` nelle
**`PublicKeyCredentialRequestOptions`**, scegli di impostarle su `preferred` poiché questo
è un buon compromesso tra usabilità e
[sicurezza](https://www.corbado.com/it/blog/come-abilitare-passkey-su-android). Ciò significa che i controlli di
sicurezza sono attivi sui dispositivi idonei, mentre la facilità d'uso viene mantenuta sui
dispositivi senza capacità biometriche.

## 10. Conclusione: tutorial sulle passkey

Speriamo che questo tutorial sulle passkey fornisca una chiara comprensione di come
implementare le passkey in modo efficace. Durante il tutorial, abbiamo esaminato i
passaggi essenziali per creare un'applicazione con passkey, concentrandoci sui concetti
fondamentali e sull'implementazione pratica. Sebbene questa guida serva come punto di
partenza, c'è molto altro da esplorare e perfezionare nel mondo di WebAuthn.

Incoraggiamo gli sviluppatori ad approfondire le sfumature delle passkey (ad esempio,
aggiungendo più passkey, verificando la prontezza dei dispositivi per le passkey o
offrendo soluzioni di recupero). È un viaggio che vale la pena intraprendere, che offre
sia sfide che immense ricompense nel migliorare l'autenticazione degli utenti. Con le
passkey, non stai solo costruendo una funzionalità; stai contribuendo a un mondo digitale
più sicuro e facile da usare.
