[SOLVED] Moralis Auth loses connection on page refresh

Hey guys,

I have used the code from Moralis to authorise users on my website (https://docs.moralis.io/docs/web3-parse-server-authentication). The code works perfectly, however upon refreshing the page, the button stops showing the connected address and the console doesn’t log the account connected.

Code:

    const { data: ownerWhoopys, isFetching: fetchingListedWhoopys, fetch } =
    useMoralisQuery("CreatedWhoopys", (query) =>
    query.matches("creatorAddress", account, 'i').limit(100).ascending("whoopyName"),
    [account] 
    )
    console.log(ownerWhoopys);
    console.log(account)

Any idea what the issue is?

That tutorial’s frontend doesn’t use moralis-v1 to connect with the Parse server so you won’t get a cached user. It just authenticates manually with the Auth API and displays the info - it doesn’t persist anywhere e.g. local storage or cookies.

You can look at the React migration client.

My authenticate button code is copied from there. Are there any specific changes I need to make? Or is there a way to simply store the info in local storage/cookies?

you can try to follow this tutorial:

and you will get to this authentication: https://docs.moralis.io/docs/connect-to-your-client#authentication

I’ve followed this tutorial and implemented everything successfully. This issue I believe (as @alex pointed out) is that users aren’t getting saved in local storage/cookies, and that’s why upon page refresh the info is lost.

As per this part in the docs: https://v1docs.moralis.io/moralis-dapp/users/current-user the user is usually cached in local storage. However, since I’m using AuthAPI, I don’t think this is happening.

Any idea how I can implement this?

are you sure that you used that tutorial that uses morals v1 sdk for authentication?

This is the code. I believe the issue is the server is using Moralis V2 for authentication. Currently the server is using Moralis.Auth. What is the equivalent of this in Moralis V1?

CLIENT SIDE

  import React, { useState } from 'react';
  import { useMoralis } from 'react-moralis';
  import Moralis from 'moralis-v1';
  
  interface AuthenticateModalProps {
    isOpen: boolean;
    onClose: () => void;
  }
  
  export const AuthenticateModal = ({ isOpen, onClose }: AuthenticateModalProps) => {
    const { authenticate, enableWeb3 } = useMoralis();
  
    const [authError, setAuthError] = useState<null | Error>(null);
    const [isAuthenticating, setIsAuthenticating] = useState(false);
  
    /**
     * 1) Connect to a Evm
     * 2) Request message to sign using the Moralis Auth Api of moralis (handled on server)
     * 3) Login via parse using the signed message (verification handled on server via Moralis Auth Api)
     */
    const handleAuth = async (provider: 'metamask' | 'walletconnect' | 'magicLink' | 'web3Auth' = 'metamask') => {
      try {
        setAuthError(null);
        setIsAuthenticating(true);
  
        // Enable web3 to get user address and chain
        await enableWeb3({ throwOnError: true, provider });
        const { account, chainId } = Moralis;
  
        if (!account) {
          throw new Error('Connecting to chain failed, as no connected account was found');
        }
        if (!chainId) {
          throw new Error('Connecting to chain failed, as no connected chain was found');
        }
  
        // Get message to sign from the auth api
        const { message } = await Moralis.Cloud.run('requestMessage', {
          address: account,
          chain: parseInt(chainId, 16),
          networkType: 'evm',
        });
  
        // Authenticate and login via parse
        await authenticate({
          signingMessage: message,
          throwOnError: true,
        });
        onClose();
      // } catch (error) {
      //   setAuthError(error);
      } finally {
        setIsAuthenticating(false);
      }
    };

SERVER SIDE (Moralis Eth Adapter)

// Note: do not import Parse dependency. see https://github.com/parse-community/parse-server/issues/6467
/* global Parse */
import Moralis from 'moralis';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function validateAuthData(authData: any) {
  const { signature, data } = authData;

  return Moralis.Auth.verify({
    message: data,
    signature,
    networkType: 'evm',
  })
    .then((result) => {
      const authenticated = result.toJSON();

      authData.chainId = result.result.chain.decimal;
      authData.nonce = authenticated.nonce;
      authData.address = result.result.address.checksum;
      authData.version = authenticated.version;
      authData.domain = authenticated.domain;
      authData.expirationTime = authenticated.expirationTime;
      authData.notBefore = authenticated.notBefore;
      authData.resources = authenticated.resources;
      authData.statement = authenticated.statement;
      authData.uri = authenticated.uri;
      authData.moralisProfileId = authenticated.profileId;
    })

    .catch(() => {
      // @ts-ignore (see note at top of file)
      throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Moralis auth failed, invalid data');
    });
}

SERVER SIDE (Moralis Eth Adapter)

import Moralis from 'moralis';
import { authRequests } from '../store';
import { ParseServerRequest } from '../utils/ParseServerRequest';

const serverRequest = new ParseServerRequest();

interface ParseUser {
  objectId: string;
}

export interface RequestMessage {
  address: string;
  chain: string;
  network: string;
}

const DOMAIN = 'defi.finance';
const STATEMENT = 'Please sign this message to confirm your identity.';
const URI = 'https://defi.finance';
const EXPIRATION_TIME = '2023-01-01T00:00:00.000Z';
const TIMEOUT = 15;

export async function requestMessage({
  address,
  chain,
  networkType,
}: {
  address: string;
  chain?: string;
  networkType: 'evm' | 'solana';
}) {
  if (networkType === 'evm' && chain) {
    return requestMessageEvm({ address, chain, networkType });
  }
  if (networkType === 'solana') {
    return requestMessageSol({ address, networkType });
  }
  throw new Error('Invalid network');
}

async function requestMessageEvm({
  address,
  chain,
  networkType,
}: {
  address: string;
  chain: string;
  networkType: 'evm';
}) {
  const result = await Moralis.Auth.requestMessage({
    address,
    chain,
    networkType,
    domain: DOMAIN,
    statement: STATEMENT,
    uri: URI,
    expirationTime: EXPIRATION_TIME,
    timeout: TIMEOUT,
  });

  const { message, id, profileId } = result.toJSON();
  authRequests.set(message, { id, profileId });

  return message;
}

async function requestMessageSol({ address, networkType }: { address: string; networkType: 'solana' }) {
  const result = await Moralis.Auth.requestMessage({
    address,
    networkType,
    solNetwork: 'devnet',
    domain: DOMAIN,
    statement: STATEMENT,
    uri: URI,
    expirationTime: EXPIRATION_TIME,
    timeout: TIMEOUT,
  });

  const { message, id, profileId } = result.toJSON();
  authRequests.set(message, { id, profileId });

  return message;
}

export interface VerifyMessage {
  network: string;
  signature: string;
  message: string;
}

export async function verifyMessage({ network, signature, message }: VerifyMessage) {
  const storedData = authRequests.get(message);

  if (!storedData) {
    throw new Error('Invalid message');
  }

  const { id: storedId, profileId: storedProfileId } = storedData;

  const authData = {
    id: storedProfileId,
    authId: storedId,
    message,
    signature,
    network,
  };

  // Authenticate
  const user = await serverRequest.post<ParseUser>({
    endpoint: `/users`,
    params: {
      authData: {
        moralis: authData,
      },
    },
    useMasterKey: true,
  });

  // Update user moralisProfile column
  await serverRequest.put({
    endpoint: `/users/${user.objectId}`,
    params: {
      moralisProfileId: storedProfileId,
    },
    useMasterKey: true,
  });

  // Get authenticated user
  const updatedUser = await serverRequest.get({
    endpoint: `/users/${user.objectId}`,
    useMasterKey: true,
  });

  return updatedUser;
}

NOTE: This is the exact code from the tutorial.

do you have a session entry in the Session table in your parse dashboard interface for that user? is there a session token?

did you look in local storage to see if there is anything saved there?

I would expect this authentication function to handle more

Yes, I am getting session token, etc. Also, just checked my local storage and I do have the session details saved there. If this is the case, do you have any idea why I’m facing the issue mentioned in the original question?

I don’t know, if you have all that data in local storage then you should be able to get current user information.

Are you trying to get current user information?

So I got the issue. This issue is that isWeb3Enabled returns false after refresh. This is my handle auth function:

    const handleAuth = async (provider: 'metamask' | 'walletconnect' | 'magicLink' | 'web3Auth' = 'metamask') => {
      try {
        setAuthError(null);
        setIsAuthenticating(true);
  
        // Enable web3 to get user address and chain
        await enableWeb3({ throwOnError: true, provider });
        const { account, chainId } = Moralis;
  
        if (!account) {
          throw new Error('Connecting to chain failed, as no connected account was found');
        }
        if (!chainId) {
          throw new Error('Connecting to chain failed, as no connected chain was found');
        }
  
        // Get message to sign from the auth api
        const { message } = await Moralis.Cloud.run('requestMessage', {
          address: account,
          chain: parseInt(chainId, 16),
          networkType: 'evm',
        });
  
        // Authenticate and login via parse
        await authenticate({
          signingMessage: message,
          throwOnError: true,
        });
        onClose();
      // } catch (error) {
      //   setAuthError(error);
      } finally {
        setIsAuthenticating(false);
      }
    };

Any idea why web3 doesn’t remain enabled after refresh?

I don’t know, you can try to enable it again

it shouldn’t depend on if web3 is enabled or not if it reads the information from current saved user

I figured this out. The issue was enableWeb3 as I suspected. The answer was really simple though. I just used a useEffect:

    useEffect(() => {
      // to avoid problems in Next.JS apps because of window object
      if (typeof window == 'undefined') return;
      if (
          !isWeb3Enabled &&
          !isWeb3EnableLoading &&
          isAuthenticated
      ) {
          enableWeb3({ throwOnError: true, provider: 'metamask' });
      }
  }, [isWeb3Enabled, isWeb3EnableLoading, isAuthenticated]);
1 Like