[SOLVED] Problems Authenticating with NextJs, next-auth, Moralis

hello, I’m trying to get authentication working with nextjs, next-auth and moralis following the docs here:
https://docs.moralis.io/docs/sign-in-with-metamask , as well as the new tutorial that went on youtube a few days ago covering the same topic.

I tried implementing this in my own project and ended up with errors during the authentication stage so I’ve stripped back to just following the tutorial exactly step by step with a fresh project and I have the same problem. I have also just downloaded the github repo with the example project and still get the same errors with that. So the following is all true given that I am using the exact same code as the docs/sample project with no modifications. I can confirm I have implemented my own .env.local files to provide the API key, nextauth secret, app domain and next-auth url. (https://github.com/MoralisWeb3/demo-apps/tree/main/nextjs_moralis_auth)

The code seems to be working up until the next-auth step where the async function authenticate(credentials) {} is called. I get thrown into the error case here and can confirm with a console.log() there.

What happens, is I can click to sign in with metamask. I will get prompted with metamask with the correct message being provided in the window, once I click on ‘sign’, I am hit with an error:
TypeError: Cannot read properties of null (reading 'auth')

I’ve attached below a screenshot of the error as well as full console print-out during the error.

When I first tried running the project I was getting errors from next-auth when installing dependancies that it is not compatible with node versions above ^16.13.0. I had the newest 18.7.0 installed so I used nvm to roll back to 16.13.0 to try. This allowed me to install dependancies without error but I still get the same behaviour when trying the app. I am also an an M1 Mac, if that makes any difference.

edit - I have now tried re-building the project with vscode and terminal launched with Rosetta to try with x86 arch instead of arm64, I get the same result.

My searching around the internet shows some other people having to do work-arounds with next-auth on new node versions such as creating new session wrappers that intercept all axios calls to inject the session token inside each request/response. I tried implementing something similar but wasn’t able to make it work.

It seems others are able to use this workflow though so I’m hoping someone can help me zone in on what’s not working.

error log:

MoralisError [Moralis SDK Core Error]: [C0006] Request failed with status 400: Request failed with status code 400
    at RequestController.makeError (/Volumes/DEV/moralis_auth/node_modules/@moralisweb3/core/lib/controllers/RequestController.js:137:20)
    at RequestController.<anonymous> (/Volumes/DEV/moralis_auth/node_modules/@moralisweb3/core/lib/controllers/RequestController.js:118:38)
    at step (/Volumes/DEV/moralis_auth/node_modules/@moralisweb3/core/lib/controllers/RequestController.js:44:23)
    at Object.throw (/Volumes/DEV/moralis_auth/node_modules/@moralisweb3/core/lib/controllers/RequestController.js:25:53)
    at rejected (/Volumes/DEV/moralis_auth/node_modules/@moralisweb3/core/lib/controllers/RequestController.js:17:65)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  isMoralisError: true,
  code: 'C0006',
  details: {
    status: 400,
    request: ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 7,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      maxRequestsOnConnectionReached: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: true,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: null,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      _closed: false,
      socket: [TLSSocket],
      _header: 'POST /challenge/verify/evm HTTP/1.1\r\n' +
        'Accept: application/json, text/plain, */*\r\n' +
        'Content-Type: application/json\r\n' +
        'x-moralis-platform: JS SDK\r\n' +
        'x-moralis-platform-version: 2.0.1\r\n' +
        'x-moralis-build-target: node\r\n' +
        'x-api-key: [redacted]\r\n' +
        'User-Agent: axios/0.27.2\r\n' +
        'Content-Length: 446\r\n' +
        'Host: auth-api.do-prod-1.moralis.io\r\n' +
        'Connection: close\r\n' +
        '\r\n',
      _keepAliveTimeout: 0,
      _onPendingData: [Function: nop],
      agent: [Agent],
      socketPath: undefined,
      method: 'POST',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      path: '/challenge/verify/evm',
      _ended: true,
      res: [IncomingMessage],
      aborted: false,
      timeoutCb: null,
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      host: 'auth-api.do-prod-1.moralis.io',
      protocol: 'https:',
      _redirectable: [Writable],
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype],
      [Symbol(kUniqueHeaders)]: null
    },
    response: {
      status: 400,
      statusText: 'Bad Request',
      headers: [Object],
      config: [Object],
      request: [ClientRequest],
      data: [Object]
    }
  },
  [cause]: [AxiosError: Request failed with status code 400] {
    code: 'ERR_BAD_REQUEST',
    config: {
      transitional: [Object],
      adapter: [Function: httpAdapter],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 10000,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      maxBodyLength: -1,
      env: [Object],
      validateStatus: [Function: validateStatus],
      headers: [Object],
      url: 'https://auth-api.do-prod-1.moralis.io/challenge/verify/evm',
      params: {},
      method: 'post',
      data: '{"message":"https://www.jasio.io wants you to sign in with your Ethereum account:\\n0xe044881312bd839409a017c5846d9eE33896059c\\n\\nPlease sign this message to confirm your identity.\\n\\nURI: http://localhost:3000\\nVersion: 1\\nChain ID: 4\\nNonce: jalxs9sl6rnq9BS55\\nIssued At: 2022-08-23T23:15:48.388Z","signature":"0x680db9883f198b33b69a4c930f8715a3111df2bc1d55793e7d99dcf90dd3356844906ccc78d46bc22580abc4a096611e2931074f8ba78130591524d98f8b2fb51c"}'
    },
    request: ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 7,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      maxRequestsOnConnectionReached: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: true,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: null,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      _closed: false,
      socket: [TLSSocket],
      _header: 'POST /challenge/verify/evm HTTP/1.1\r\n' +
        'Accept: application/json, text/plain, */*\r\n' +
        'Content-Type: application/json\r\n' +
        'x-moralis-platform: JS SDK\r\n' +
        'x-moralis-platform-version: 2.0.1\r\n' +
        'x-moralis-build-target: node\r\n' +
        'x-api-key: [redacted]\r\n' +
        'User-Agent: axios/0.27.2\r\n' +
        'Content-Length: 446\r\n' +
        'Host: auth-api.do-prod-1.moralis.io\r\n' +
        'Connection: close\r\n' +
        '\r\n',
      _keepAliveTimeout: 0,
      _onPendingData: [Function: nop],
      agent: [Agent],
      socketPath: undefined,
      method: 'POST',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      path: '/challenge/verify/evm',
      _ended: true,
      res: [IncomingMessage],
      aborted: false,
      timeoutCb: null,
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      host: 'auth-api.do-prod-1.moralis.io',
      protocol: 'https:',
      _redirectable: [Writable],
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype],
      [Symbol(kUniqueHeaders)]: null
    },
    response: {
      status: 400,
      statusText: 'Bad Request',
      headers: [Object],
      config: [Object],
      request: [ClientRequest],
      data: [Object]
    }
  }
}

package.json

{
    "name": "nextjs_moralis_auth",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "lint": "next lint"
    },
    "dependencies": {
        "axios": "^0.27.2",
        "ethers": "^5.6.9",
        "moralis": "2.0.1",
        "next": "^12.2.4",
        "next-auth": "^4.10.3",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "wagmi": "^0.6.1"
    }
}

/signin.jsx

import { MetaMaskConnector } from 'wagmi/connectors/metaMask';
import { signIn } from 'next-auth/react';
import { useAccount, useConnect, useSignMessage, useDisconnect } from 'wagmi';
import { useRouter } from 'next/router';
import axios from 'axios';

function SignIn() {
    const { connectAsync } = useConnect();
    const { disconnectAsync } = useDisconnect();
    const { isConnected } = useAccount();
    const { signMessageAsync } = useSignMessage();
    const { push } = useRouter();

    const handleAuth = async () => {
        if (isConnected) {
            await disconnectAsync();
        }

        const { account, chain } = await connectAsync({ connector: new MetaMaskConnector() });

        const userData = { address: account, chain: chain.id, network: 'evm' };

        const { data } = await axios.post('/api/auth/request-message', userData, {
            headers: {
                'content-type': 'application/json',
            },
        });

        const message = data.message;

        const signature = await signMessageAsync({ message });

        // redirect user after success authentication to '/user' page
        const { url } = await signIn('credentials', { message, signature, redirect: false, callbackUrl: '/user' });
        /**
         * instead of using signIn(..., redirect: "/user")
         * we get the url from callback and push it to the router to avoid page refreshing
         */
        push(url);
    };

    return (
        <div>
            <h3>Web3 Authentication</h3>
            <button onClick={() => handleAuth()}>Authenticate via Metamask</button>
        </div>
    );
}

export default SignIn;

/_app.js

import { createClient, configureChains, defaultChains, WagmiConfig } from 'wagmi';
import { publicProvider } from 'wagmi/providers/public';
import { SessionProvider } from 'next-auth/react';

const { provider, webSocketProvider } = configureChains(defaultChains, [publicProvider()]);

const client = createClient({
    provider,
    webSocketProvider,
    autoConnect: true,
});

function MyApp({ Component, pageProps }) {
    return (
        <WagmiConfig client={client}>
            <SessionProvider session={pageProps.session} refetchInterval={0}>
                <Component {...pageProps} />
            </SessionProvider>
        </WagmiConfig>
    );
}

export default MyApp;

/api/auth/[…nextauth].js

import CredentialsProvider from 'next-auth/providers/credentials';
import NextAuth from 'next-auth';
import Moralis from 'moralis';

export default NextAuth({
    providers: [
        CredentialsProvider({
            name: 'MoralisAuth',
            credentials: {
                message: {
                    label: 'Message',
                    type: 'text',
                    placeholder: '0x0',
                },
                signature: {
                    label: 'Signature',
                    type: 'text',
                    placeholder: '0x0',
                },
            },
            async authorize(credentials) {
                try {
                    const { message, signature } = credentials;

                    await Moralis.start({ apiKey: process.env.MORALIS_API_KEY });

                    const { address, profileId, expirationTime } = (await Moralis.Auth.verify({ message, signature, network: 'evm' })).raw;

                    const user = { address, profileId, expirationTime, signature };

                    return user;
                } catch (e) {
                    // eslint-disable-next-line no-console
                    console.error(e);
                    return null;
                }
            },
        }),
    ],
    callbacks: {
        async jwt({ token, user }) {
            user && (token.user = user);
            return token;
        },
        async session({ session, token }) {
            session.expires = token.user.expirationTime;
            session.user = token.user;
            return session;
        },
    },
    session: {
        strategy: 'jwt',
    },
});

/api/auth/request-message.js

import Moralis from 'moralis';

const config = {
    domain: process.env.APP_DOMAIN,
    statement: 'Please sign this message to confirm your identity.',
    uri: process.env.NEXTAUTH_URL,
    timeout: 60,
};

export default async function handler(req, res) {
    const { address, chain, network } = req.body;

    await Moralis.start({ apiKey: process.env.MORALIS_API_KEY });

    try {
        const message = await Moralis.Auth.requestMessage({
            address,
            chain,
            network,
            ...config,
        });

        res.status(200).json(message);
    } catch (error) {
        res.status(400).json({ error });
        console.error(error);
    }
}

1 Like

These are the versions I have in a copy of the tutorial that works locally - try using the latest moralis in particular and start from scratch if you’re just copying the package.json over:

  "axios": "0.27.2",
    "ethers": "5.7.0",
    "moralis": "^2.0.3",
    "next": "12.2.5",
    "next-auth": "4.10.3",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "wagmi": "0.6.4"

This user also tried with Node v16.13.1.

I just tried those versions, still getting the same result.

currently tracking some similar issues with next-auth and nextjs versions as discussed here:

Users are reporting the issue was fixed in next 12.2.5 however, that isn’t working for me thought. I’ve tried the specific canary versions 2,3, and 5 too.

Also, not sure if it’s relevant but I am getting the same web3 api key from moralis when selecting either the V3 or V2 options. Is it possible I’m not getting the correct V3 api key / would that matter?

your initial error also shows unauthorized, so maybe check if the callbacks in [...nextauth] page is properly added as per the tutorial.

And if possible try getting more info from the network tab or vs code terminal for more details on error.

thanks, the […nextauth].js file is exactly as from the tutorial with the addition of adding logic around the expiration time as added in the project example on github. I tried before without that as well so it is copy paste from the docs page and was getting the same result. The code is in starting post.

the full error log from vscode terminal is in my original post below the image screenshot also. I’ll go see what I can grab from the network tab in browser.

edit: the ‘credentials’ fetch call is the errored one, details attached below, let me know if there’s anything else i can provide -

i having the same issue also in an M1

1 Like

I tried ruling out hardware by deploying a test project to Vercel - I was still getting the same errors in the deployment. If I have time today I’ll try building it on another machine to be sure.

You can check out a running example here which is also deployed to vercel

wich version of node are you running?

Am using v16.15.0 currently, but it isn’t really required to get it running because it worked for me using 16.0.0 and 17.9.0

1 Like

Hey guys so I think this issue is specific to the arm64 arch of the M1 (possibly M2 also). I just tried doing the project from one of my older laptops (still mac, but intel chips) and it worked without issue. For reference I built using node 16.17.0 (latest LTS), which I have also tried on the M1. Looks like some package somewhere in the pile is not compatible? I’ve tried doing everything with Rosetta + i386 terminal on the M1 to no avail.

EDIT - actually nevermind… I was just able to build the demo app on my M1 and it worked this time. No idea what I did differently right now to be honest. I will try to incorporate into my main project I was trying this on and see if I can figure it out.

EDIT2 -
So I tried my main project again and I was getting the null error. I started by deleting my node_modules folder and re-installed everything via yarn, still errors. I checked my package.json against the now-working version of the moralis example project and found that my next, ethers and wagmi versions were higher (^12.2.5, ^5.7.0, ^0.6.4) vs ^12.2.4, ^5.6.9, ^0.6.1. So, I downgraded them all to match the working project.

Somewhere after deleting my node_modules and rebuilding with the new versions a couple times, the auth started working. A strange thing, I updated each one back to the newest version one at a time and checked for function along the way to see if I could isolate one as the issue, everything remained working until I’m back at ^12.2.5, ^5.7.0, ^0.6.4 (newest) respectively with a working auth.

Leaves me a little bit at a loss what the exact issue has been :thinking: :man_shrugging:

I will try seeing how it behaves deployed in live production soon. For @Marche , perhaps you can try following some of the steps I did, no rosetta needed in the end, node 16.17.0 used.

Also I’ll have to double check against next-auth docs whether it matters but I’ve noticed that the nextauth secret when generated using openssl rand -hex 32 produces a 64-digit number for me which I’m using now as opposed to the https://generate-secret.now.sh/32 link which generates a 32-digit number.

5 Likes

hey i can confirm that downgrading that dependencies fixe the problem thanks @spaceleafio!!