"Invalid Certificate" Error

Hi everyone,

I’ve been facing a recurring “Invalid certificate” error when working locally with the KYC actor on the Internet Computer (IC). The issue arises when I try to interact with the KYC actor, such as checking user existence, fetching usernames, or verifying KYC status.

  • The error appears inconsistently but frequently.

  • It mostly happens after some time of inactivity or after restarting my local environment. function MyNavbar() {
    const { isAuthenticated, login, logout, principal, kycActor } = useAuth();
    const [principals, setPrincipal] = useState(“”);
    const [isOpen, setIsOpen] = useState(false);
    const [copied, setCopied] = useState(false);
    const navigate = useNavigate(); // Use navigate for redirection
    const [userExists, setUserExists] = useState(false); // State to track if user exists
    const [isUserChecked, setIsUserChecked] = useState(false); // State to track if the user check is complete
    const [username, setUsername] = useState(“”); // State to store the username
    const [isDropdownOpen, setIsDropdownOpen] = useState(false);

    const handleLogin = async () => {
    await login();
    if (principal) {
    try {
    const exists = await kycActor.has_username_for_principal(principal);
    setUserExists(exists);
    setIsUserChecked(true); // Mark the user check as complete

      if (exists) {
        const fetchedUsername = await kycActor.get_username_by_principal(principal); // Fetch the username
        setUsername(fetchedUsername.Ok || "Unknown User"); // Extract the Ok value, handle errors
      }
    } catch (error) {
      console.error("Error checking user existence:", error);
      console.error("Detailed Error:", error);
      console.error("Principal:", principal);
      console.error("KYC Actor:", kycActor);
      setIsUserChecked(true);  // Ensure we handle error state
    
      // Handle specific errors
      if (error.message.includes("Invalid certificate")) {
        console.error("Certificate error: Please re-authenticate.");
        // Optionally, prompt the user to log in again
        await login();
      }
    }
    

    }
    };

    useEffect(() => {
    if (isAuthenticated) {
    try {
    handleLogin();
    setPrincipal(principal);
    } catch (error) {
    console.error(“Failed to fetch principal:”, error);
    setPrincipal(“Error fetching principal”);
    }
    }
    }, [isAuthenticated, principal]);

    // Effect to handle redirection based on user existence
    useEffect(() => {
    if (isUserChecked) { // Ensure we navigate only after checking the user status
    if (!userExists) {
    navigate(‘/signup’);
    } else {
    navigate(‘/’);
    }
    }
    }, [userExists, isUserChecked]); // Depend on userExists and isUserChecked

    useEffect(() => {
    if (!isAuthenticated) {
    setIsDropdownOpen(false); // Close the dropdown when the user logs out
    }
    }, [isAuthenticated]);

    const userInitial = username ? username.charAt(0).toUpperCase() : “U”; ///////////////////this is myuse auth file import { AuthClient } from “@dfinity/auth-client”;
    import React, { createContext, useContext, useEffect, useState } from “react”;
    import { canisterId as MercxId, createActor as createMercxActor } from “…/…/declarations/mercx_backend”;
    import { canisterId, createActor } from “…/…/declarations/icrc1_ledger_canister”;
    import { canisterId as icrcIndexCanisterId, createActor as createIndexActor } from “…/…/declarations/icrc1_index_canister”;
    import { canisterId as icpCanisterId, createActor as createIcpActor } from “…/…/declarations/icp_ledger_canister”;
    import { canisterId as tommyCanisterId, createActor as createTommyActor } from “…/…/declarations/tommy_icrc1_ledger”;
    import { canisterId as kycCanisterId, createActor as createKycActor } from “…/…/declarations/kyc”;
    import { canisterId as fxmxCanisterId, createActor as createFXMXActor } from “…/…/declarations/fxmx_icrc1_ledger”;
    import { canisterId as fxmxIndexId, createActor as createFXMXindexActor } from “…/…/declarations/fxmx_icrc1_index”;

// Create a React Context for sharing authentication status across the component tree
const AuthContext = createContext();
function detectInAppBrowser() {
const ua = navigator.userAgent.toLowerCase();
return (
ua.includes(“linkedin”) ||
ua.includes(“fban”) || ua.includes(“fbav”) || // Facebook
ua.includes(“instagram”) ||
ua.includes(“twitter”) ||
ua.includes(“edge”) || ua.includes(“edg”)
);
}

function handleRedirect() {
if (detectInAppBrowser()) {
if (window.location.pathname !== “/InappBrowser”) {
window.location.href = “/InappBrowser”; // Redirect only if NOT already there
}
}
}

// Function to determine the correct identity provider URL based on environment and browser
export const getIdentityProvider = () => {
let idpProvider;
// Safeguard against server rendering
// Check if the code is running in a browser environment
if (typeof window !== “undefined”) {
// Determine if the environment is local (not production)
const isLocal = process.env.DFX_NETWORK !== “ic”;
// Safari does not support localhost subdomains
// Check if the user’s browser is Safari to handle specific limitations
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (isLocal && isSafari) {
// Safari handling for local development environment
idpProvider = http://localhost:8001/?canisterId=${process.env.CANISTER_ID_INTERNET_IDENTITY};
} else if (isLocal) {
// General handling for non-Safari browsers in local development
idpProvider = http://${process.env.CANISTER_ID_INTERNET_IDENTITY}.localhost:8001;
}
}
return idpProvider;
};

// Default options for the authentication client
export const defaultOptions = {
/**

  • @type {import(“@dfinity/auth-client”).AuthClientCreateOptions}
    /
    createOptions: {
    idleOptions: {
    // Set to true if you do not want idle functionality
    // Opt to disable idle functionality for the auth client
    disableIdle: true,
    },
    },
    /
    *
  • @type {import(“@dfinity/auth-client”).AuthClientLoginOptions}
    */
    loginOptions: {
    // Specify the identity provider determined by getIdentityProvider
    identityProvider: getIdentityProvider(),
    },
    };

/**
*

  • @param options - Options for the AuthClient
  • @param {AuthClientCreateOptions} options.createOptions - Options for the AuthClient.create() method
  • @param {AuthClientLoginOptions} options.loginOptions - Options for the AuthClient.login() method
  • @returns
    */
    // Custom hook to manage authentication state
    export const useAuthClient = (options = defaultOptions) => {
    const [isAuthenticated, setIsAuthenticated] = useState(false);
    const [authClient, setAuthClient] = useState(null);
    const [identity, setIdentity] = useState(null);
    const [principal, setPrincipal] = useState(null);
    const [whoamiActor, setWhoamiActor] = useState(null);
    const [icrcIndexActor, setIcrcIndexActor] = useState(null);
    const [icpActor, setIcpActor] = useState(null);
    const [mercx_Actor, setMercxActor] = useState(null);
    const [tommy_Actor, setTommyActor] = useState(null);
    const [kycActor, setKycActor] = useState(null); // :white_check_mark: KYC actor
    const [fxmxActor, setFXMXActor] = useState(null);
    const [fxmxIndexActor, setFXMXindexActor] = useState(null);

useEffect(() => {
handleRedirect();
// openInExternalBrowser();
// Initialize AuthClient
// Create an AuthClient instance when the component mounts
AuthClient.create(options.createOptions).then(async (client) => {

  updateClient(client);

});

}, );

// Function to handle user login
const login = () => {
authClient.login({
…options.loginOptions,
onSuccess: () => {
updateClient(authClient);
},

});

};

// Update local state with the new auth client details
async function updateClient(client) {
const isAuthenticated = await client.isAuthenticated();
setIsAuthenticated(isAuthenticated);

const identity = client.getIdentity();
setIdentity(identity);

const principal = identity.getPrincipal();
setPrincipal(principal);

setAuthClient(client);

// Create an actor with the authenticated identity
const actor = createActor(canisterId, {
  agentOptions: {
    identity,
  },
});

setWhoamiActor(actor);
const indexActor = createIndexActor(icrcIndexCanisterId, {
  agentOptions: {
    identity,
  },
});
setIcrcIndexActor(indexActor);

const IcpActor = createIcpActor(icpCanisterId, {
  agentOptions: {
    identity,
  },
});
setIcpActor(IcpActor);

const MercxActor = createMercxActor(MercxId, {
  agentOptions: {
    identity,
  },
});
setMercxActor(MercxActor);

const tommyActor = createTommyActor(tommyCanisterId, {
  agentOptions: {
    identity,
  },
});
setTommyActor(tommyActor);

// ✅ Create the KYC Actor
const kycActor = createKycActor(kycCanisterId, {
  agentOptions: {
    identity,
  },
});

setKycActor(kycActor);

const FxmxActor = createFXMXActor(fxmxCanisterId, {
  agentOptions: {
    identity,
  },
});
setFXMXActor(FxmxActor);

const FxmxindexActor = createFXMXindexActor(fxmxIndexId, {
  agentOptions: {
    identity,
  },
});
setFXMXindexActor(FxmxindexActor);

}

async function logout() {
await authClient?.logout();
await updateClient(authClient);
}

return {
isAuthenticated,
login,
logout,
authClient,
identity,
principal,
whoamiActor,
icrcIndexActor,
icpActor,
mercx_Actor,
tommy_Actor,
kycActor, // :white_check_mark: Return KYC actor
fxmxActor,
fxmxIndexActor,
};
};

/**

  • @type {React.FC}
    */
    // Provider component to wrap the application and provide auth state via context
    export const AuthProvider = ({ children }) => {
    const auth = useAuthClient();

return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
};

//A simple hook that allows any component within the context provider to access the auth state and methods easily.
export const useAuth = () => useContext(AuthContext);

Sorry, the code is tricky to read. Are you integrating with Internet Identity? Do you use delegations?

Delegations expire, so it’s normal that you might get an “invalid certificate” after inactivity or time. Do you get it if you refresh the delegation?