diff --git a/examples/code-flow/src/index.tsx b/examples/code-flow/src/index.tsx
index 1bc8501..e4b5086 100644
--- a/examples/code-flow/src/index.tsx
+++ b/examples/code-flow/src/index.tsx
@@ -15,6 +15,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
clientId={"b44c68f0-e5b3-4a1d-a3e3-df8632b0223b"}
sessionExpiration={timeout}
domain={"gizmette.local.com"}
+ debug
>
diff --git a/examples/code-flow/src/main.tsx b/examples/code-flow/src/main.tsx
index af788f0..152f0a9 100644
--- a/examples/code-flow/src/main.tsx
+++ b/examples/code-flow/src/main.tsx
@@ -10,45 +10,27 @@ export const App = ({ timeout }: { timeout: string }) => {
logout,
isAuthenticated,
getAccessToken,
- isLoading,
registeringForPasskey,
loginWithPasskey,
} = useAuth();
const [apiResponse, setApiResponse] = useState({ data: "" });
- console.info({ isAuthenticated, isLoading });
-
- const logger = console;
- logger.log("isAuthenticated", isAuthenticated);
- logger.log("isLoading", isLoading);
const handleValidLogin = async (e: { preventDefault: () => void }) => {
e.preventDefault();
- const response = await login(
+ await login(
process.env.PUBLIC_TEST_USER as string,
process.env.PUBLIC_TEST_USER_PASSWORD as string,
AUTH_TYPES.CODE,
);
- if (!response) {
- console.error(`==> [${Date.now()}] : `, "Login failed");
- } else {
- console.info(`==> [${Date.now()}] : `, "Login successful");
- console.info(`==> [${Date.now()}] : `, response);
- }
};
const handleInvalidLogin = async (e: { preventDefault: () => void }) => {
e.preventDefault();
- const response = await login(
+ await login(
process.env.PUBLIC_TEST_USER as string,
"invalid-password",
AUTH_TYPES.CODE,
);
- if (!response) {
- console.error(`==> [${Date.now()}] : `, "Login failed");
- } else {
- console.info(`==> [${Date.now()}] : `, "Login successful");
- console.info(`==> [${Date.now()}] : `, response);
- }
};
const handleValidAPICall = async (e: { preventDefault: () => void }) => {
diff --git a/packages/auth-provider/bundlesize.config.js b/packages/auth-provider/bundlesize.config.js
index 6c95f15..6cf9688 100644
--- a/packages/auth-provider/bundlesize.config.js
+++ b/packages/auth-provider/bundlesize.config.js
@@ -9,7 +9,7 @@ export default {
*/
{
path: "dist/index.js",
- limit: "21 kb",
+ limit: "18 kb",
},
],
};
diff --git a/packages/auth-provider/package.json b/packages/auth-provider/package.json
index f11fd5c..a99b9e5 100644
--- a/packages/auth-provider/package.json
+++ b/packages/auth-provider/package.json
@@ -14,9 +14,7 @@
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
- "files": [
- "dist"
- ],
+ "files": ["dist"],
"scripts": {
"build:check": "tsc",
"build:js": "vite build",
@@ -45,8 +43,8 @@
},
"dependencies": {
"@simplewebauthn/browser": "10.0.0",
- "@thumbmarkjs/thumbmarkjs": "0.14.8",
"@versini/auth-common": "workspace:../auth-common",
+ "@versini/ui-fingerprint": "1.0.1",
"@versini/ui-hooks": "4.0.1",
"jose": "5.6.3",
"uuid": "10.0.0"
diff --git a/packages/auth-provider/src/common/types.d.ts b/packages/auth-provider/src/common/types.d.ts
index b6bab2c..6deea13 100644
--- a/packages/auth-provider/src/common/types.d.ts
+++ b/packages/auth-provider/src/common/types.d.ts
@@ -25,6 +25,7 @@ export type AuthProviderProps = {
clientId: string;
accessType?: string;
domain?: string;
+ debug?: boolean;
};
export type AuthState = {
@@ -36,6 +37,7 @@ export type AuthState = {
userId?: string;
username?: string;
};
+ debug?: boolean;
};
export type LoginType = (
diff --git a/packages/auth-provider/src/common/utilities.ts b/packages/auth-provider/src/common/utilities.ts
index aaa0b56..5688f75 100644
--- a/packages/auth-provider/src/common/utilities.ts
+++ b/packages/auth-provider/src/common/utilities.ts
@@ -1,4 +1,3 @@
-import { getFingerprint } from "@thumbmarkjs/thumbmarkjs";
import {
API_TYPE,
AUTH_TYPES,
@@ -6,6 +5,7 @@ import {
JWT,
verifyAndExtractToken,
} from "@versini/auth-common";
+import { getFingerprintHash } from "@versini/ui-fingerprint";
import { API_ENDPOINT } from "./constants";
import type { ServiceCallProps } from "./types";
@@ -379,13 +379,7 @@ export const graphQLCall = async ({
export const getCustomFingerprint = async () => {
try {
- const res = await getFingerprint();
- if (typeof res === "string") {
- return res;
- } else if (res.hash && typeof res.hash === "string") {
- return res.hash;
- }
- return "";
+ return await getFingerprintHash();
} catch (_error) {
return "";
}
diff --git a/packages/auth-provider/src/components/AuthProvider/AuthProvider.tsx b/packages/auth-provider/src/components/AuthProvider/AuthProvider.tsx
index 4c638c8..0ea120b 100644
--- a/packages/auth-provider/src/components/AuthProvider/AuthProvider.tsx
+++ b/packages/auth-provider/src/components/AuthProvider/AuthProvider.tsx
@@ -42,6 +42,7 @@ export const AuthProvider = ({
sessionExpiration,
clientId,
domain = "",
+ debug = false,
}: AuthProviderProps) => {
const [state, dispatch] = useReducer(reducer, {
isLoading: true,
@@ -49,9 +50,11 @@ export const AuthProvider = ({
authenticationType: null,
user: undefined,
logoutReason: "",
+ debug,
});
const effectDidRunRef = useRef(false);
+ const fingerprintRef = useRef("");
const [idToken, setIdToken, , removeIdToken] = useLocalStorage({
key: `${LOCAL_STORAGE_PREFIX}::${clientId}::@@user@@`,
@@ -67,11 +70,23 @@ export const AuthProvider = ({
const [nonce, setNonce, , removeNonce] = useLocalStorage({
key: `${LOCAL_STORAGE_PREFIX}::${clientId}::@@nonce@@`,
});
+
+ const logger = useCallback(
+ (...args: unknown[]) => {
+ if (debug) {
+ console.info(`==> [Auth ${Date.now()}]: `, ...args);
+ }
+ },
+ [debug],
+ );
const tokenManager = new TokenManager(accessToken, refreshToken);
const removeStateAndLocalStorage = useCallback(
(logoutReason?: string) => {
- console.warn(logoutReason);
+ logger(
+ "removeStateAndLocalStorage: removing state and local storage with reason: ",
+ logoutReason,
+ );
dispatch({
type: ACTION_TYPE_LOGOUT,
payload: {
@@ -84,11 +99,12 @@ export const AuthProvider = ({
removeNonce();
dispatch({ type: ACTION_TYPE_LOADING, payload: { isLoading: false } });
},
- [removeAccessToken, removeIdToken, removeNonce, removeRefreshToken],
+ [removeAccessToken, removeIdToken, removeNonce, removeRefreshToken, logger],
);
const invalidateAndLogout = useCallback(
async (message: string) => {
+ logger("invalidateAndLogout: invalidating and logging out");
const { user } = state;
await logoutUser({
userId: user?.userId || "",
@@ -108,9 +124,27 @@ export const AuthProvider = ({
idToken,
refreshToken,
removeStateAndLocalStorage,
+ logger,
],
);
+ /**
+ * This effect is responsible to set the fingerprintRef value when the
+ * component is first loaded.
+ */
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: logger is stable
+ useEffect(() => {
+ (async () => {
+ logger("useEffect: setting the fingerprint");
+ fingerprintRef.current = await getCustomFingerprint();
+ })();
+ return () => {
+ logger("useEffect: cleaning up the fingerprint");
+ fingerprintRef.current = "";
+ };
+ }, []);
+
/**
* This effect is responsible to set the authentication state based on the
* idToken stored in the local storage. It is used when the page is being
@@ -125,6 +159,7 @@ export const AuthProvider = ({
try {
const jwt = await verifyAndExtractToken(idToken);
if (jwt && jwt.payload[JWT.USER_ID_KEY] !== "") {
+ logger("useEffect: setting the authentication state");
dispatch({
type: ACTION_TYPE_LOGIN,
payload: {
@@ -136,19 +171,24 @@ export const AuthProvider = ({
},
});
} else {
+ logger("useEffect: invalid JWT, invalidating and logging out");
await invalidateAndLogout(EXPIRED_SESSION);
}
} catch (_error) {
+ logger(
+ "useEffect: exception validating JWT, invalidating and logging out",
+ );
await invalidateAndLogout(EXPIRED_SESSION);
}
})();
} else {
+ logger("useEffect: setting the loading state to false");
dispatch({ type: ACTION_TYPE_LOADING, payload: { isLoading: false } });
}
return () => {
effectDidRunRef.current = true;
};
- }, [state.isLoading, idToken, invalidateAndLogout]);
+ }, [state.isLoading, idToken, invalidateAndLogout, logger]);
const login: LoginType = async (username, password, type) => {
const _nonce = uuidv4();
@@ -158,9 +198,10 @@ export const AuthProvider = ({
removeAccessToken();
removeRefreshToken();
+ logger("login: Logging in with type: ", type);
+
if (type === AUTH_TYPES.CODE) {
const { code_verifier, code_challenge } = await pkceChallengePair();
-
const preResponse = await getPreAuthCode({
nonce: _nonce,
clientId,
@@ -178,7 +219,7 @@ export const AuthProvider = ({
code: preResponse.code,
code_verifier,
domain,
- fingerprint: await getCustomFingerprint(),
+ fingerprint: fingerprintRef.current,
});
if (response.status) {
setIdToken(response.idToken);
@@ -210,7 +251,7 @@ export const AuthProvider = ({
nonce: _nonce,
type,
domain,
- fingerprint: await getCustomFingerprint(),
+ fingerprint: fingerprintRef.current,
});
if (response.status) {
setIdToken(response.idToken);
@@ -242,6 +283,7 @@ export const AuthProvider = ({
try {
if (isAuthenticated && user && user.userId) {
if (accessToken) {
+ logger("getAccessToken");
const jwtAccess = await verifyAndExtractToken(accessToken);
if (jwtAccess && jwtAccess.payload[JWT.USER_ID_KEY] !== "") {
return accessToken;
@@ -251,6 +293,7 @@ export const AuthProvider = ({
* accessToken is not valid, so we need to try to refresh it using the
* refreshToken - this is a silent refresh.
*/
+ logger("getAccessToken: invalid access token, refreshing it");
const res = await tokenManager.refreshtoken({
clientId,
userId: user.userId as string,
@@ -265,12 +308,19 @@ export const AuthProvider = ({
/**
* refreshToken is not valid, so we need to re-authenticate the user.
*/
+ logger("getAccessToken: invalid refresh token, re-authenticating user");
await invalidateAndLogout(ACCESS_TOKEN_ERROR);
return "";
}
+ logger(
+ "getAccessToken: user is not authenticated, cannot get access token",
+ );
await invalidateAndLogout(ACCESS_TOKEN_ERROR);
return "";
} catch (_error) {
+ logger(
+ "getAccessToken: exception occurred, invalidating and logging out",
+ );
await invalidateAndLogout(ACCESS_TOKEN_ERROR);
return "";
}
@@ -333,6 +383,8 @@ export const AuthProvider = ({
removeAccessToken();
removeRefreshToken();
+ logger("loginWithPasskey");
+
const temporaryAnonymousUserId = uuidv4();
let response = await graphQLCall({
accessToken,
@@ -356,7 +408,7 @@ export const AuthProvider = ({
authentication,
nonce: _nonce,
domain,
- fingerprint: await getCustomFingerprint(),
+ fingerprint: fingerprintRef.current,
},
});
diff --git a/packages/auth-provider/src/components/AuthProvider/InternalContext.ts b/packages/auth-provider/src/components/AuthProvider/InternalContext.ts
index 8adb9ab..d5e9287 100644
--- a/packages/auth-provider/src/components/AuthProvider/InternalContext.ts
+++ b/packages/auth-provider/src/components/AuthProvider/InternalContext.ts
@@ -11,6 +11,7 @@ export const InternalContext = React.createContext<{
authenticationType: null,
user: undefined,
logoutReason: "",
+ debug: false,
},
dispatch: () => {},
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 438530a..88bfd58 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -149,12 +149,12 @@ importers:
'@simplewebauthn/browser':
specifier: 10.0.0
version: 10.0.0
- '@thumbmarkjs/thumbmarkjs':
- specifier: 0.14.8
- version: 0.14.8
'@versini/auth-common':
specifier: workspace:../auth-common
version: link:../auth-common
+ '@versini/ui-fingerprint':
+ specifier: 1.0.1
+ version: 1.0.1
'@versini/ui-hooks':
specifier: 4.0.1
version: 4.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1516,9 +1516,6 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
- '@thumbmarkjs/thumbmarkjs@0.14.8':
- resolution: {integrity: sha512-J+/HnYBv24ufFCoyqbFhtZk1zTGKWzHfyN15sc6kJq/1D/6H1oaKt+W1yWxkUwzclMS1n36EaoHbNjFeiuDqDg==}
-
'@tokenizer/token@0.3.0':
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
@@ -1683,6 +1680,9 @@ packages:
react: ^18.3.1
react-dom: ^18.3.1
+ '@versini/ui-fingerprint@1.0.1':
+ resolution: {integrity: sha512-V7Gtyk2FU7lRv3+qcWEHtvTV3XbZSVm9Q2/t28DChQZO89MMHWZQRfrfFH2UV30j8aRxDX0Z/jao0+ZOZ9Ixbg==}
+
'@versini/ui-hooks@4.0.1':
resolution: {integrity: sha512-LiYYRGg5j1jFyQniJVx/Ke8hXaXIHBo7LPt0MyJR5b7FfF0AbxoIt8AfQrpYFERvjpq8kXlib0ELMdC0nbO4yw==}
peerDependencies:
@@ -7567,8 +7567,6 @@ snapshots:
dependencies:
'@testing-library/dom': 10.3.1
- '@thumbmarkjs/thumbmarkjs@0.14.8': {}
-
'@tokenizer/token@0.3.0': {}
'@tufjs/canonical-json@2.0.0': {}
@@ -7905,6 +7903,8 @@ snapshots:
transitivePeerDependencies:
- ts-node
+ '@versini/ui-fingerprint@1.0.1': {}
+
'@versini/ui-hooks@4.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
react: 18.3.1