From 10c97c4e3d7a767d2500dc476fcda3053d500fac Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Wed, 31 May 2023 16:28:01 -0500 Subject: [PATCH 1/6] Login sooner --- 01-Login/public/js/ui.js | 3 +- 02-Calling-an-API/index.html | 4 +- .../public/js/{app.js => auth0-vanilla.js} | 42 ++++++++++++++++++- 3 files changed, 43 insertions(+), 6 deletions(-) rename 02-Calling-an-API/public/js/{app.js => auth0-vanilla.js} (78%) diff --git a/01-Login/public/js/ui.js b/01-Login/public/js/ui.js index 5f79e98..6e383b2 100644 --- a/01-Login/public/js/ui.js +++ b/01-Login/public/js/ui.js @@ -1,8 +1,7 @@ // URL mapping, from hash to a function that responds to that URL action const router = { "/": () => showContent("content-home"), - "/profile": () => - requireAuth(() => showContent("content-profile"), "/profile"), + "/profile": () => requireAuth(() => showContent("content-profile"), "/profile"), "/login": () => login() }; diff --git a/02-Calling-an-API/index.html b/02-Calling-an-API/index.html index c3ce340..3bc5e13 100644 --- a/02-Calling-an-API/index.html +++ b/02-Calling-an-API/index.html @@ -29,6 +29,8 @@ rel="stylesheet" href="https://cdn.auth0.com/js/auth0-samples-theme/1.0/css/auth0-theme.min.css" /> + + @@ -312,9 +314,7 @@
Result
- - diff --git a/02-Calling-an-API/public/js/app.js b/02-Calling-an-API/public/js/auth0-vanilla.js similarity index 78% rename from 02-Calling-an-API/public/js/app.js rename to 02-Calling-an-API/public/js/auth0-vanilla.js index ca8a1a0..d43d6b2 100644 --- a/02-Calling-an-API/public/js/app.js +++ b/02-Calling-an-API/public/js/auth0-vanilla.js @@ -55,10 +55,13 @@ const configureClient = async () => { auth0Client = await auth0.createAuth0Client({ domain: config.domain, clientId: config.clientId, + cacheLocation: config.cacheLocation, + useRefreshTokens: true, authorizationParams: { audience: config.audience } - }); + + }); }; /** @@ -102,9 +105,11 @@ const callApi = async () => { } }; +const auth0ClientPromise = configureClient(); + // Will run when page finishes loading window.onload = async () => { - await configureClient(); + await auth0ClientPromise; // If unable to parse the history hash, default to the root URL if (!showContentFromUrl(window.location.pathname)) { @@ -158,7 +163,40 @@ window.onload = async () => { } window.history.replaceState({}, document.title, "/"); + } else { + const isAuthenticated = await auth0Client.isAuthenticated(); + if (!isAuthenticated) { + return login(window.location.origin); + } } updateUI(); }; + +(async function(){ + await auth0ClientPromise; + const isAuthenticated = await auth0Client.isAuthenticated(); + if (!isAuthenticated) { + const query = window.location.search; + const shouldParseResult = query.includes("code=") && query.includes("state="); + + if (shouldParseResult) { + console.log("> Parsing redirect"); + try { + const result = await auth0Client.handleRedirectCallback(); + + console.log("Logged in!"); + } catch (err) { + console.log("Error parsing redirect:", err); + } + + window.history.replaceState({}, document.title, "/"); + + } else { + const isAuthenticated = await auth0Client.isAuthenticated(); + if (!isAuthenticated) { + return login(window.location.origin); + } + } + } +}) (); From 0f63ae366c6d9bc268f3472387d6c20df56778b7 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 1 Jun 2023 11:29:20 -0500 Subject: [PATCH 2/6] Set tab width to 2 --- 02-Calling-an-API/.prettierrc.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/02-Calling-an-API/.prettierrc.yaml b/02-Calling-an-API/.prettierrc.yaml index 57c485f..ea61662 100644 --- a/02-Calling-an-API/.prettierrc.yaml +++ b/02-Calling-an-API/.prettierrc.yaml @@ -1,2 +1,4 @@ semi: true arrowParens: always +tabWidth: 2 + From 074bdd00c0fca0c7fface1667cc33acc0fe85dc2 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 1 Jun 2023 11:51:49 -0500 Subject: [PATCH 3/6] Refactor into JS modules --- 02-Calling-an-API/index.html | 11 ++- 02-Calling-an-API/public/js/auth0-client.js | 84 +++++++++++++++++++ 02-Calling-an-API/public/js/auth0-ui.js | 91 +++++++++++++++++++++ 02-Calling-an-API/public/js/ui.js | 15 ++-- 4 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 02-Calling-an-API/public/js/auth0-client.js create mode 100644 02-Calling-an-API/public/js/auth0-ui.js diff --git a/02-Calling-an-API/index.html b/02-Calling-an-API/index.html index 3bc5e13..917005e 100644 --- a/02-Calling-an-API/index.html +++ b/02-Calling-an-API/index.html @@ -30,7 +30,9 @@ href="https://cdn.auth0.com/js/auth0-samples-theme/1.0/css/auth0-theme.min.css" /> - + + + @@ -69,7 +71,6 @@
  • - Log out + Log out
  • @@ -315,6 +314,6 @@
    Result
    - + diff --git a/02-Calling-an-API/public/js/auth0-client.js b/02-Calling-an-API/public/js/auth0-client.js new file mode 100644 index 0000000..9b434ff --- /dev/null +++ b/02-Calling-an-API/public/js/auth0-client.js @@ -0,0 +1,84 @@ +//import { createAuth0Client } from "@auth0/auth0-spa-js"; +// Normally this would be the import statement above. +// For this example we do not have client dependencies installed via npm. +const createAuth0Client = globalThis.auth0.createAuth0Client; + +let auth0Client; + +const fetchAuthConfig = () => fetch("/auth_config.json"); + +/** + * + * @returns Auth0Client + */ +async function createClient() { + const response = await fetchAuthConfig(); + const config = await response.json(); + return await createAuth0Client({ + domain: config.domain, + clientId: config.clientId, + cacheLocation: config.cacheLocation, + useRefreshTokens: true, + authorizationParams: { + audience: config.audience, + }, + }); +} + +/** + * Starts the authentication flow + */ +const login = async (targetUrl) => { + try { + console.log("Logging in", targetUrl); + + const options = { + authorizationParams: { + redirect_uri: window.location.origin, + }, + }; + + if (targetUrl) { + options.appState = { targetUrl }; + } + + await auth0Client.loginWithRedirect(options); + } catch (err) { + console.log("Log in failed", err); + } +}; + +/** + * Executes the logout flow + */ +const logout = async () => { + try { + console.log("Logging out"); + await auth0Client.logout({ + logoutParams: { + returnTo: window.location.origin, + }, + }); + } catch (err) { + console.log("Log out failed", err); + } +}; + +/** + * Checks to see if the user is authenticated. If so, `fn` is executed. Otherwise, the user + * is prompted to log in + * @param {*} fn The function to execute if the user is logged in + */ +const requireAuth = async (fn, targetUrl) => { + const isAuthenticated = await auth0Client.isAuthenticated(); + + if (isAuthenticated) { + return fn(); + } + + return login(targetUrl); +}; + +auth0Client = await createClient(); + +export { auth0Client, login, logout, requireAuth }; diff --git a/02-Calling-an-API/public/js/auth0-ui.js b/02-Calling-an-API/public/js/auth0-ui.js new file mode 100644 index 0000000..123156e --- /dev/null +++ b/02-Calling-an-API/public/js/auth0-ui.js @@ -0,0 +1,91 @@ +import { auth0Client, requireAuth, login } from "./auth0-client.js"; +import { + showContentFromUrl, + updateUI, + isRouteLink, + eachElement, +} from "./ui.js"; + +/** + * Calls the API endpoint with an authorization token + */ +const callApi = async () => { + try { + const token = await auth0Client.getTokenSilently(); + + const response = await fetch("/api/external", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + const responseData = await response.json(); + const responseElement = document.getElementById("api-call-result"); + + responseElement.innerText = JSON.stringify(responseData, {}, 2); + + document.querySelectorAll("pre code").forEach(hljs.highlightBlock); + + eachElement(".result-block", (c) => c.classList.add("show")); + } catch (e) { + console.error(e); + } +}; + +// Will run when page finishes loading because of defer +// If unable to parse the history hash, default to the root URL +if (!showContentFromUrl(window.location.pathname)) { + showContentFromUrl("/"); + window.history.replaceState({ url: "/" }, {}, "/"); +} + +const bodyElement = document.getElementsByTagName("body")[0]; + +// Listen out for clicks on any hyperlink that navigates to a #/ URL +bodyElement.addEventListener("click", (e) => { + if (isRouteLink(e.target)) { + const url = e.target.getAttribute("href"); + + if (showContentFromUrl(url)) { + e.preventDefault(); + window.history.pushState({ url }, {}, url); + } + } else if (e.target.getAttribute("id") === "call-api") { + e.preventDefault(); + callApi(); + } +}); + +const isAuthenticated = await auth0Client.isAuthenticated(); + +if (isAuthenticated) { + console.log("> User is authenticated"); + window.history.replaceState({}, document.title, window.location.pathname); + updateUI(); +} else { + console.log("> User not authenticated"); + + const query = window.location.search; + const shouldParseResult = query.includes("code=") && query.includes("state="); + + if (shouldParseResult) { + console.log("> Parsing redirect"); + try { + const result = await auth0Client.handleRedirectCallback(); + + if (result.appState && result.appState.targetUrl) { + showContentFromUrl(result.appState.targetUrl); + } + + console.log("Logged in!"); + } catch (err) { + console.log("Error parsing redirect:", err); + } + + window.history.replaceState({}, document.title, "/"); + } else { + login(); + } + + updateUI(); +} diff --git a/02-Calling-an-API/public/js/ui.js b/02-Calling-an-API/public/js/ui.js index 024c3b2..6969b1a 100644 --- a/02-Calling-an-API/public/js/ui.js +++ b/02-Calling-an-API/public/js/ui.js @@ -1,3 +1,5 @@ +import { auth0Client, requireAuth, login, logout } from "./auth0-client.js"; + // URL mapping, from hash to a function that responds to that URL action const router = { "/": () => showContent("content-home"), @@ -6,7 +8,7 @@ const router = { requireAuth(() => showContent("content-profile"), "/profile"), "/external-api": () => requireAuth(() => showContent("content-external-api"), "/external-api"), - "/login": () => login() + "/login": () => login(), }; //Declare helper functions @@ -17,7 +19,7 @@ const router = { * @param {*} selector The CSS selector to find * @param {*} fn The function to execute for every element */ -const eachElement = (selector, fn) => { +export const eachElement = (selector, fn) => { for (let e of document.querySelectorAll(selector)) { fn(e); } @@ -29,7 +31,7 @@ const eachElement = (selector, fn) => { * router, defined above. * @param {*} url The route URL */ -const showContentFromUrl = (url) => { +export const showContentFromUrl = (url) => { if (router[url]) { router[url](); return true; @@ -42,7 +44,7 @@ const showContentFromUrl = (url) => { * Returns true if `element` is a hyperlink that can be considered a link to another SPA route * @param {*} element The element to check */ -const isRouteLink = (element) => +export const isRouteLink = (element) => element.tagName === "A" && element.classList.contains("route-link"); /** @@ -60,7 +62,7 @@ const showContent = (id) => { /** * Updates the user interface */ -const updateUI = async () => { +export const updateUI = async () => { try { const isAuthenticated = await auth0Client.isAuthenticated(); @@ -75,6 +77,9 @@ const updateUI = async () => { document.querySelectorAll("pre code").forEach(hljs.highlightBlock); + eachElement("#qsLoginBtn", (e) => e.addEventListener("click", login)); + eachElement("#qsLogoutBtn", (e) => e.addEventListener("click", logout)); + eachElement(".profile-image", (e) => (e.src = user.picture)); eachElement(".user-name", (e) => (e.innerText = user.name)); eachElement(".user-email", (e) => (e.innerText = user.email)); From 888f19805a56eb070b2f7c2a39ffc693eb65cef4 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 1 Jun 2023 11:56:11 -0500 Subject: [PATCH 4/6] Remove unused code --- 02-Calling-an-API/public/js/auth0-vanilla.js | 202 ------------------- 1 file changed, 202 deletions(-) delete mode 100644 02-Calling-an-API/public/js/auth0-vanilla.js diff --git a/02-Calling-an-API/public/js/auth0-vanilla.js b/02-Calling-an-API/public/js/auth0-vanilla.js deleted file mode 100644 index d43d6b2..0000000 --- a/02-Calling-an-API/public/js/auth0-vanilla.js +++ /dev/null @@ -1,202 +0,0 @@ -// The Auth0 client, initialized in configureClient() -let auth0Client = null; - -/** - * Starts the authentication flow - */ -const login = async (targetUrl) => { - try { - console.log("Logging in", targetUrl); - - const options = { - authorizationParams: { - redirect_uri: window.location.origin - } - }; - - if (targetUrl) { - options.appState = { targetUrl }; - } - - await auth0Client.loginWithRedirect(options); - } catch (err) { - console.log("Log in failed", err); - } -}; - -/** - * Executes the logout flow - */ -const logout = async () => { - try { - console.log("Logging out"); - await auth0Client.logout({ - logoutParams: { - returnTo: window.location.origin - } - }); - } catch (err) { - console.log("Log out failed", err); - } -}; - -/** - * Retrieves the auth configuration from the server - */ -const fetchAuthConfig = () => fetch("/auth_config.json"); - -/** - * Initializes the Auth0 client - */ -const configureClient = async () => { - const response = await fetchAuthConfig(); - const config = await response.json(); - - auth0Client = await auth0.createAuth0Client({ - domain: config.domain, - clientId: config.clientId, - cacheLocation: config.cacheLocation, - useRefreshTokens: true, - authorizationParams: { - audience: config.audience - } - - }); -}; - -/** - * Checks to see if the user is authenticated. If so, `fn` is executed. Otherwise, the user - * is prompted to log in - * @param {*} fn The function to execute if the user is logged in - */ -const requireAuth = async (fn, targetUrl) => { - const isAuthenticated = await auth0Client.isAuthenticated(); - - if (isAuthenticated) { - return fn(); - } - - return login(targetUrl); -}; - -/** - * Calls the API endpoint with an authorization token - */ -const callApi = async () => { - try { - const token = await auth0Client.getTokenSilently(); - - const response = await fetch("/api/external", { - headers: { - Authorization: `Bearer ${token}` - } - }); - - const responseData = await response.json(); - const responseElement = document.getElementById("api-call-result"); - - responseElement.innerText = JSON.stringify(responseData, {}, 2); - - document.querySelectorAll("pre code").forEach(hljs.highlightBlock); - - eachElement(".result-block", (c) => c.classList.add("show")); - } catch (e) { - console.error(e); - } -}; - -const auth0ClientPromise = configureClient(); - -// Will run when page finishes loading -window.onload = async () => { - await auth0ClientPromise; - - // If unable to parse the history hash, default to the root URL - if (!showContentFromUrl(window.location.pathname)) { - showContentFromUrl("/"); - window.history.replaceState({ url: "/" }, {}, "/"); - } - - const bodyElement = document.getElementsByTagName("body")[0]; - - // Listen out for clicks on any hyperlink that navigates to a #/ URL - bodyElement.addEventListener("click", (e) => { - if (isRouteLink(e.target)) { - const url = e.target.getAttribute("href"); - - if (showContentFromUrl(url)) { - e.preventDefault(); - window.history.pushState({ url }, {}, url); - } - } else if (e.target.getAttribute("id") === "call-api") { - e.preventDefault(); - callApi(); - } - }); - - const isAuthenticated = await auth0Client.isAuthenticated(); - - if (isAuthenticated) { - console.log("> User is authenticated"); - window.history.replaceState({}, document.title, window.location.pathname); - updateUI(); - return; - } - - console.log("> User not authenticated"); - - const query = window.location.search; - const shouldParseResult = query.includes("code=") && query.includes("state="); - - if (shouldParseResult) { - console.log("> Parsing redirect"); - try { - const result = await auth0Client.handleRedirectCallback(); - - if (result.appState && result.appState.targetUrl) { - showContentFromUrl(result.appState.targetUrl); - } - - console.log("Logged in!"); - } catch (err) { - console.log("Error parsing redirect:", err); - } - - window.history.replaceState({}, document.title, "/"); - } else { - const isAuthenticated = await auth0Client.isAuthenticated(); - if (!isAuthenticated) { - return login(window.location.origin); - } - } - - updateUI(); -}; - -(async function(){ - await auth0ClientPromise; - const isAuthenticated = await auth0Client.isAuthenticated(); - if (!isAuthenticated) { - const query = window.location.search; - const shouldParseResult = query.includes("code=") && query.includes("state="); - - if (shouldParseResult) { - console.log("> Parsing redirect"); - try { - const result = await auth0Client.handleRedirectCallback(); - - console.log("Logged in!"); - } catch (err) { - console.log("Error parsing redirect:", err); - } - - window.history.replaceState({}, document.title, "/"); - - } else { - const isAuthenticated = await auth0Client.isAuthenticated(); - if (!isAuthenticated) { - return login(window.location.origin); - } - } - } -}) (); From ff0257a08110af1951fc8a326a0c2b7679613c3a Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 1 Jun 2023 12:21:42 -0500 Subject: [PATCH 5/6] Document code --- 02-Calling-an-API/public/js/auth0-client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02-Calling-an-API/public/js/auth0-client.js b/02-Calling-an-API/public/js/auth0-client.js index 9b434ff..8260fbe 100644 --- a/02-Calling-an-API/public/js/auth0-client.js +++ b/02-Calling-an-API/public/js/auth0-client.js @@ -8,7 +8,7 @@ let auth0Client; const fetchAuthConfig = () => fetch("/auth_config.json"); /** - * + * Called once to create a single instance of the Auth0 SDK client * @returns Auth0Client */ async function createClient() { From 9a96678aa4f510f3154f8039f0984c562e75f6a8 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 1 Jun 2023 12:24:30 -0500 Subject: [PATCH 6/6] Use const for auth0Client --- 02-Calling-an-API/public/js/auth0-client.js | 4 +--- 02-Calling-an-API/public/js/auth0-ui.js | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/02-Calling-an-API/public/js/auth0-client.js b/02-Calling-an-API/public/js/auth0-client.js index 8260fbe..9420f45 100644 --- a/02-Calling-an-API/public/js/auth0-client.js +++ b/02-Calling-an-API/public/js/auth0-client.js @@ -3,8 +3,6 @@ // For this example we do not have client dependencies installed via npm. const createAuth0Client = globalThis.auth0.createAuth0Client; -let auth0Client; - const fetchAuthConfig = () => fetch("/auth_config.json"); /** @@ -79,6 +77,6 @@ const requireAuth = async (fn, targetUrl) => { return login(targetUrl); }; -auth0Client = await createClient(); +const auth0Client = await createClient(); export { auth0Client, login, logout, requireAuth }; diff --git a/02-Calling-an-API/public/js/auth0-ui.js b/02-Calling-an-API/public/js/auth0-ui.js index 123156e..3f15c87 100644 --- a/02-Calling-an-API/public/js/auth0-ui.js +++ b/02-Calling-an-API/public/js/auth0-ui.js @@ -84,6 +84,7 @@ if (isAuthenticated) { window.history.replaceState({}, document.title, "/"); } else { + // Treat all pages as protected and redirect to login when not yet authenticated login(); }