keystone-nextjs-auth icon indicating copy to clipboard operation
keystone-nextjs-auth copied to clipboard

Invalid Json Response for /api/auth/session

Open foopis23 opened this issue 2 years ago • 3 comments

Both in my own application and in the example application, im getting this error. When I run yarn keystone dev it just goes into an infinite loop of compiling and erroring out.

For all I know im doing something wrong, but I follow the documentation here and I also checked next-auth's documentation and I have googled this issue and found somewhat related issues, but I think its a pretty general error since its just the fetch api not being able to read an expected json output.

Since the unexpected character is < I assumed the response it was getting was html and when I tried to go, to the page it was trying to get to, it seemed like it was returning an html page saying that I didn't have access to this page. But adding the path to the public pages config did nothing.

Here is my main file, its basically the example project but I changed the database to sqlite because it was easier to run locally that way.

import 'dotenv/config';
import * as Path from 'path';
import { config } from '@keystone-6/core';
import { statelessSessions } from '@keystone-6/core/session';
import Auth0 from '@opensaas/keystone-nextjs-auth/providers/auth0';
import { createAuth } from '@opensaas/keystone-nextjs-auth';
import { KeystoneContext } from '@keystone-6/core/types';
import { lists } from './schemas';

let sessionSecret = process.env.SESSION_SECRET;

if (!sessionSecret) {
  if (process.env.NODE_ENV === 'production') {
    throw new Error('The SESSION_SECRET environment variable must be set in production');
  } else {
    sessionSecret = '-- DEV COOKIE SECRET; CHANGE ME --';
  }
}

const sessionMaxAge = 60 * 60 * 24 * 30; // 30 days

const auth = createAuth({
  listKey: 'User',
  identityField: 'subjectId',
  sessionData: `id name email`,
  autoCreate: true,
  resolver: async ({ user, profile }: { user: any; profile: any }) => {
    const name = user.name as string;
    const email = profile.email as string;
    return { email, name };
  },
  pages: {
    signIn: '/admin/auth/signin',
  },
  keystonePath: '/admin',
  sessionSecret,
  providers: [
    Auth0({
      clientId: process.env.AUTH0_CLIENT_ID || 'Auth0ClientID',
      clientSecret: process.env.AUTH0_CLIENT_SECRET || 'Auth0ClientSecret',
      issuer: process.env.AUTH0_ISSUER_BASE_URL || 'https://opensaas.au.auth0.com',
    }),
  ],
});

export default auth.withAuth(
  config({
    server: {
      cors: {
        origin: [process.env.FRONTEND || 'http://localhost:7777'],
        credentials: true,
      },
    },
    db: {
      provider: 'sqlite',
      url: 'file:./keystone.db',
    },
    ui: {
      isAccessAllowed: (context: KeystoneContext) => !!context.session?.data,
      publicPages: ['/admin/auth/signin', '/admin/auth/error'],
      getAdditionalFiles: [
        async () => [
          {
            mode: 'copy',
            inputPath: Path.resolve('./customPages/signin.js'),
            outputPath: 'pages/auth/signin.js',
          },
          {
            mode: 'copy',
            inputPath: Path.resolve('./customPages/error.js'),
            outputPath: 'pages/auth/error.js',
          },
        ],
      ],
    },
    lists,
    session: statelessSessions({
      maxAge: sessionMaxAge,
      secret: sessionSecret,
    }),
    experimental: {
      generateNodeAPI: true,
    },
  })
);

Here is what the whole error log looks like

➜ yarn keystone dev
yarn run v1.22.19
$ /Users/eric/Development/web/keystonejs-auth-test/node_modules/.bin/keystone dev
✨ Starting Keystone
⭐️ Dev Server Starting on http://localhost:3000
⭐️ GraphQL API Starting on http://localhost:3000/api/graphql
✨ Generating GraphQL and Prisma schemas
✨ The database is already in sync with the Prisma schema.
✨ Connecting to the database
✨ Creating server
✅ GraphQL API ready
✨ Generating Admin UI code
✨ Preparing Admin UI app
event - compiled client and server successfully in 7.4s (1263 modules)
✅ Admin UI ready
wait  - compiling /no-access (client and server)...
event - compiled client and server successfully in 492 ms (1268 modules)
[next-auth][error][CLIENT_FETCH_ERROR]
https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0 {
  error: {
    message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0',
    stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0\n' +
      '    at /Users/eric/Development/web/keystonejs-auth-test/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
    name: 'FetchError'
  },
  url: 'http://localhost:3000/api/auth/session',
  message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0'
}
wait  - compiling /api/__keystone_api_build (client and server)...
event - compiled successfully in 129 ms (159 modules)
wait  - compiling...
event - compiled client and server successfully in 174 ms (1290 modules)
[next-auth][error][CLIENT_FETCH_ERROR]
https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0 {
  error: {
    message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0',
    stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0\n' +
      '    at /Users/eric/Development/web/keystonejs-auth-test/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
      '    at runMicrotasks (<anonymous>)\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
    name: 'FetchError'
  },
  url: 'http://localhost:3000/api/auth/session',
  message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0'
}

foopis23 avatar Sep 30 '22 15:09 foopis23

Updated, I built and ran it with yarn keystone start and actually that endpoint is a 404 page, not a no access page.

foopis23 avatar Sep 30 '22 17:09 foopis23

Oh, I figured it out. It was that I needed to set the NEXTAUTH_URL because it needs to be /admin/api/auth not /api/auth. Pretty new to keystone and im primarily a vue developer so don't have a lot of experience with the base next-auth library. That said, I think NEXTAUTH_URL should be mentioned in more than just the contributing section and honestly, it might be nice if it the package just checked if you set the NEXTAUTH_URL and if not it set it to /admin/api/auth because I think that is the default for keystone?

foopis23 avatar Sep 30 '22 18:09 foopis23

Hey @foopis23 Thanks for posting your solution. NEXTAUTH_URL should be being set to this default if you don't set it, I'll have to dig around and see why this might not be happening.

borisno2 avatar Oct 10 '22 14:10 borisno2

I'm having this problem too but with the Azure AD provider. I created a new keystonejs app with the following:

import * as dotenv from 'dotenv';
dotenv.config();
import { config } from '@keystone-6/core';
import { statelessSessions } from '@keystone-6/core/session';
import AzureADProvider from "@opensaas/keystone-nextjs-auth/providers/azure-ad";
import { createAuth } from '@opensaas/keystone-nextjs-auth';
import { lists } from './schema';

let sessionSecret = process.env.SESSION_SECRET;

if (!sessionSecret) {
  if (process.env.NODE_ENV === 'production') {
    throw new Error('The SESSION_SECRET environment variable must be set in production');
  } else {
    sessionSecret = '-- DEV COOKIE SECRET; CHANGE ME --';
  }
}
const sessionMaxAge = 60 * 60 * 24 * 30; // 30 days

const auth = createAuth({
  listKey: 'User',
  identityField: 'subjectId',
  sessionData: `id name email`,
  sessionSecret: sessionSecret,
  autoCreate: true,
  resolver: async ({user, profile, account}) => {
    const username = user.name as string;
    const email = user.email as string;
    return { email, username };
  },
  keystonePath: '/admin',
  providers: [
    AzureADProvider({
      clientId: process.env.AZURE_AD_CLIENT_ID,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
      tenantId: process.env.AZURE_AD_TENANT_ID,
    }),    
  ],
});

export default auth.withAuth(
  config({
    db: {
      provider: 'mysql',
      url: process.env.DATABASE_URL,
      useMigrations: true,
      idField: { kind: 'uuid' },
      enableLogging: true,
    },
    lists,
    session: statelessSessions({
      maxAge: sessionMaxAge,
      secret: sessionSecret,
    })
  })
);

I have tried setting NEXTAUTH_URL but this doesn't seem to make any difference. I have tried all of:

NEXTAUTH_URL="http://localhost:3000/admin/api/auth"
NEXTAUTH_URL="http://localhost:3000/admin"
NEXTAUTH_URL="http://localhost:3000"

Still receiving the following error:

[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0 {
  error: {
    message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0',
    stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0\n' +
      '    at /home/enablenz/projects/enzweb/api/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
      '    at runMicrotasks (<anonymous>)\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
    name: 'FetchError'
  },
  url: 'http://localhost:3000/api/auth/session',
  message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0'
}

Any idea what I'm doing wrong? 🙂

earthwytch avatar Nov 29 '22 21:11 earthwytch

Hey @earthwytch based on your config NEXTAUTH_URL=http://localhost:3000/admin/api/auth should work assuming there is nothing overriding the PORT of 3000. What do you see if you open a browser to http://localhost:3000/admin/api/auth ?

borisno2 avatar Nov 29 '22 22:11 borisno2

Hi @borisno2, I get "You don't have access to this page." which is the same for http://localhost:3000. I feel like I'm just missing one tiny piece of the puzzle 😕

Output from console is the same

yarn run v1.22.19
$ ./node_modules/@keystone-6/core/bin/cli.js dev
✨ Starting Keystone
⭐️ Server listening on :::3000 (http://localhost:3000/)
⭐️ GraphQL API available at /api/graphql
✨ Generating GraphQL and Prisma schemas
✨ Your database is up to date, no migrations need to be created or applied
✨ Connecting to the database
✨ Creating server
✅ GraphQL API ready
✨ Generating Admin UI code
✨ Preparing Admin UI app
event - compiled client and server successfully in 3.5s (1183 modules)
✅ Admin UI ready
wait  - compiling /_error (client and server)...
event - compiled client and server successfully in 298 ms (1184 modules)
warn  - Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works
wait  - compiling /no-access (client and server)...
event - compiled client and server successfully in 129 ms (1189 modules)
[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0 {
  error: {
    message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0',
    stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0\n' +
      '    at /home/enablenz/projects/enzweb/api/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
      '    at runMicrotasks (<anonymous>)\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
    name: 'FetchError'
  },
  url: 'http://localhost:3000/api/auth/session',
  message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0'
}
[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0 {
  error: {
    message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0',
    stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0\n' +
      '    at /home/enablenz/projects/enzweb/api/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
      '    at runMicrotasks (<anonymous>)\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
    name: 'FetchError'
  },
  url: 'http://localhost:3000/api/auth/session',
  message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0'
}

EDIT: Also, just FYI, these are the env vars I have set... am I missing any?

AZURE_AD_CLIENT_ID=<secret squirrel here>
AZURE_AD_CLIENT_SECRET=<secret squirrel here>
AZURE_AD_TENANT_ID=<secret squirrel here>
DATABASE_URL=<secret squirrel here>
NEXTAUTH_URL=http://localhost:3000/admin/api/auth
NODE_ENV=development
SESSION_SECRET=<secret squirrel here>

earthwytch avatar Nov 29 '22 23:11 earthwytch

Could you try adding isAccessAllowed: (context: KeystoneContext) => !!context.session, to your config.ui

So that it looks something like

export default auth.withAuth(
  config({
    db: {
      provider: 'mysql',
      url: process.env.DATABASE_URL,
      useMigrations: true,
      idField: { kind: 'uuid' },
      enableLogging: true,
    },
    ui: {
      isAccessAllowed: (context: KeystoneContext) => !!context.session,
    },
    lists,
    session: statelessSessions({
      maxAge: sessionMaxAge,
      secret: sessionSecret,
    })
  })
);

borisno2 avatar Nov 29 '22 23:11 borisno2

That moved things along so I now have access to an Azure AD login button 👍

The error still appears in the console, exactly as before (same path, without "admin") but doesn't seem to affect functionality.

[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0 {
  error: {
    message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0',
    stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0\n' +
      '    at /home/enablenz/projects/test/keystone-azuread/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
    name: 'FetchError'
  },
  url: 'http://localhost:3000/api/auth/session',
  message: 'invalid json response body at http://localhost:3000/api/auth/session reason: Unexpected token < in JSON at position 0'
}

But for me it now hits an error regarding the username field, which appears in the console after going through the Microsoft authentication. No user is created in the database and it just halts at an error screen.

SELECT `db`.`User`.`id`, `db`.`User`.`name`, `db`.`User`.`email`, `db`.`User`.`subjectId`, `db`.`User`.`password`, `db`.`User`.`createdAt` FROM `db`.`User` WHERE `db`.`User`.`subjectId` = ? LIMIT ? OFFSET ? /* traceparent=00-00-00-00 */

GraphQLError: Variable "$data" got invalid value { subjectId: "<a long string id>", email: "<my email address here>", username: "<my user name here>" }; Field "username" is not defined by type "UserCreateInput". Did you mean "name"?

Apologies if this is an unrelated error (due to my lack of knowledge) but hoping you'll see something obvious 🙂

earthwytch avatar Dec 01 '22 22:12 earthwytch

That great! I have just merged #266 which should resolve the error when isAccessAllowed is not defined.

As for the second error, this would be something in your resolver function. I am guessing you are passing through a username field that gets passed into a create function in the package, however you don't have a username field in your schema. Feel free to log another issue if you are still stuck, but I will close this one as the original issue has been resolved.

borisno2 avatar Dec 04 '22 21:12 borisno2