Popup OAuth ~ Guida Pratica

Scritto da Francesco Di Donato • 27 aprile 2022 • 4 minuti di lettura
In un post precedente abbiamo implementato il flusso OAuth di GitHub da zero. Tuttavia, la reindirizzazione alla pagina di autenticazione avveniva tramite una reindirizzazione diretta, che è sicuramente un comportamento indesiderato, specialmente quando si tratta di una SPA.
Non sorprende che OAuth nel mondo reale utilizzi un approccio popup. Facile da risolvere, la reindirizzazione avverrà comunque, ma in un popup generato che, prima di chiudersi, passerà il access_token
alla scheda principale.
Dove Ci Siamo Fermati
Questo post deve essere letto come un miglioramento del precedente, quindi ha un ritmo leggermente più veloce. In caso di dubbi, si prega di fare riferimento al codice di accompagnamento 🔗 o al post precedente 🔗.
<a id="oauth-github-popup">Authenticate via GitHub (popup)</a>
<script>
const CLIENT_ID = "Iv1.395930440f268143";
const url = new URL("/login/oauth/authorize", "https://github.com");
url.searchParams.set("client_id", CLIENT_ID);
const cta = document.getElementById("oauth-github-popup");
cta.setAttribute("href", url);
cta.addEventListener("click", (event) => {
event.preventDefault();
// ...
Assembliamo il link di autorizzazione OAuth di GitHub e lo attacchiamo al tag <a>
per ragioni di accessibilità. Tuttavia, ascoltiamo anche il clic su di esso, prevenendo la reindirizzazione diretta predefinita.
Utilizzando un popup
Successivamente, utilizzeremo l’API Web window.open 🔗 per generare il popup. Si aspetta come terzo parametro una stringa contenente width
, height
e altro.
Personalmente, preferisco l’esplicità di un oggetto che viene poi convertito nella stringa sopra menzionata.
Le proprietà
top
eleft
hanno il valoreauto
- questo non è nelle specifiche dell’API, infatti viene interpretato dal seguente frammento come un’istruzione per posizionarsi al centro dell’asse relativo. Fondamentalmente, se entrambi sonoauto
, il popup si aprirà sempre al centro, anche se cambiwidth
oheight
.
// in the 'click' eventListener callback
const features = {
popup: "yes",
width: 600,
height: 700,
top: "auto",
left: "auto",
toolbar: "no",
menubar: "no",
};
const strWindowsFeatures = Object.entries(features)
.reduce((str, [key, value]) => {
if (value == "auto") {
if (key === "top") {
const v = Math.round(
window.innerHeight / 2 - features.height / 2
);
str += `top=${v},`;
} else if (key === "left") {
const v = Math.round(
window.innerWidth / 2 - features.width / 2
);
str += `left=${v},`;
}
return str;
}
str += `${key}=${value},`;
return str;
}, "")
.slice(0, -1); // remove last ',' (comma)
window.open(url, "_blank", strWindowsFeatures);
Quindi, l’utente clicca sul tuo elegante pulsante di GitHub e si autentica tramite il popup. Ma è importante istruire il server a rimandare a una pagina la cui funzione è:
- Assicurarsi che sia un popup
- Estrarre
access_token
dai parametri della query - Inviarlo alla finestra principale (
window.opener
) - Chiudersi 🔗
**Ricorda: callback OAuth Una volta che l’utente è autenticato, GitHub reindirizza all’URL di Callback specificato durante la creazione dell’OAuth App/GitHub App. Questo è trattato in modo più dettagliato nel post precedente.
server.get("/oauth/github/login/callback", async (request, reply) => {
const { code } = request.query;
const exchangeURL = new URL("login/oauth/access_token", "https://github.com");
exchangeURL.searchParams.set("client_id", process.env.CLIENT_ID);
exchangeURL.searchParams.set("client_secret", process.env.CLIENT_SECRET);
exchangeURL.searchParams.set("code", code);
const response = await axios.post(exchangeURL.toString(), null, {
headers: {
Accept: "application/json",
},
});
const { access_token } = response.data;
const redirectionURL = new URL("popup", "http://localhost:3000");
redirectionURL.searchParams.set("access_token", access_token);
reply.status(302).header("Location", redirectionURL).send();
});
server.get("/popup", (request, reply) => {
return reply.sendFile("popup.html");
});
Il client viene reindirizzato a /popup
e viene mostrato popup.html
.
<script>
if (window.opener == null) window.location = "/";
const access_token = new URL(window.location).searchParams.get(
"access_token"
);
window.opener.postMessage(access_token);
window.close();
</script>
window.opener
è null
se la pagina non è stata aperta tramite window.open
. In questo modo, se l’utente accede direttamente a /popup
, viene reindirizzato a /
.
Il calcolo è minimo, dovrebbe essere piuttosto veloce. Mostrare un caricatore, però, può solo giovarti.
Suggerimento Le SPA e le loro soluzioni di routing offrono alcuni metodi
beforeEnter
che possono essere associati a una rotta; beh, potresti controllare il valore diwindow.opener
lì, per fornire un’esperienza ancora migliore.
Quasi fatto! Il popup sta restituendo l’access_token
alla finestra principale, ma non sta ascoltando!
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
if (event.origin !== window.location.origin) {
console.warn(`Message received by ${event.origin}; IGNORED.`);
return;
}
const access_token = event.data;
}
Come precauzione, ignora qualsiasi messaggio proveniente da un’altra origine. Si prega di considerare removeEventListener
nel proprio codice.
Salva l’access_token
da qualche parte. Da questo punto, il flusso si riunisce al post precedente 🔗.
Non c’è nulla che ti impedisca di utilizzare questo schema anche per l’installazione dell’app GitHub e le modifiche ai permessi.