NextAuth - Type error: Property 'accessToken' does not exist on type 'Session'
Asked Answered
M

3

7

I am trying to deploy my application. When I try to build on my local machine it works fine, but when the build runs on GitHub it is not working.

For information, I am using yarn and yarn build to build the project.

According to most of my research, it should not be an issue. Maybe it comes from typescript, or I made a mistake I do not see, or it could be a wrong configuration of the GitHub CI? I do not know...

my code for [...nextauth].ts

import NextAuth, { NextAuthOptions } from "next-auth"
import GoogleProvider from "next-auth/providers/google"

const GOOGLE_AUTHORIZATION_URL =
  "https://accounts.google.com/o/oauth2/v2/auth?" +
  new URLSearchParams({
    prompt: "consent",
    access_type: "offline",
    response_type: "code",
  })

/**
 * Takes a token, and returns a new token with updated
 * `accessToken` and `accessTokenExpires`. If an error occurs,
 * returns the old token and an error property
 */
async function refreshAccessToken(token: any) {
  try {
    const url =
      "https://oauth2.googleapis.com/token?" +
      new URLSearchParams({
        client_id: process.env.GOOGLE_CLIENT_ID!.toString(),
        client_secret: process.env.GOOGLE_CLIENT_SECRET!.toString(),
        grant_type: "refresh_token",
        refresh_token: token.refreshToken,
      })

    const response = await fetch(url, {
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      method: "POST",
    })

    const refreshedTokens = await response.json()

    if (!response.ok) {
      throw refreshedTokens
    }

    return {
      ...token,
      accessToken: refreshedTokens.access_token,
      accessTokenExpires: Date.now() + refreshedTokens.expires_at * 1000,
      refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
    }
  } catch (error) {
    console.error("Oups", error)

    return {
      ...token,
      error: "RefreshAccessTokenError",
    }
  }
}

// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export const authOptions: NextAuthOptions = {
  // https://next-auth.js.org/configuration/providers/oauth
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
      authorization: GOOGLE_AUTHORIZATION_URL,
    }),
  ],

  theme: {
    colorScheme: "auto", // "auto" | "dark" | "light"
    brandColor: "#004821", // Hex color code
    logo: "XXX", // Absolute URL to image
  },

  secret: process.env.NEXTAUTH_SECRET,

  callbacks: {
    async jwt({ token, user, account }) {
      // Initial sign in
      if (account && user) {
        return {
          accessToken: account.access_token,
          accessTokenExpires: Date.now() + account!.expires_at! * 1000,
          refreshToken: account.refresh_token,
          user,
        }
      }

      // Return previous token if the access token has not expired yet
      if (Date.now() < token.accessTokenExpires!) {
        return token
      }

      // Access token has expired, try to update it
      return refreshAccessToken(token)
    },
    async session({ session, token }) {
      session.user = token.user!,
      session.accessToken = token.accessToken
      session.error = token.error

      return session
    },
  },

}

export default NextAuth(authOptions)

And the error I have is the following:

Failed to compile.

./src/pages/api/auth/[...nextauth].ts:99:15
Type error: Property 'accessToken' does not exist on type 'Session'.

   97 |     async session({ session, token }) {
   98 |       session.user = token.user!,
>  99 |       session.accessToken = token.accessToken
      |               ^
  100 |       session.error = token.error
  101 | 
  102 |       return session
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Process completed with exit code 1.

EDIT: So with the different comments I got, I think that the issue was due to an issue with the next auth version.

Monostich answered 13/12, 2022 at 13:9 Comment(4)
The versions of what Github CI and your local machine might not match, this change happened semi recently: github.com/nextauthjs/next-auth/pull/4953/…Squash
I recommend cleaning out your node_modules in case your version is behind what CI is usingSquash
So, my node_modules is not push on GitHub. I tried to rm -rf node_modules, and I used yarn to download them again. It still builds on my local machine. I tried to change the node version in the CI for the exact same one as on my machine and I still have the same issue. Note: It is my first time using the GitHub CIMonostich
So, I have changed the version of nextauth for 4.12.0, and I do not have this issue.Monostich
A
16

First, you need to add a folder called types in the root of your project and then add a file called next-auth.d.ts with the following content:

// my-project/types/next-auth.d.ts

import NextAuth from 'next-auth'

declare module 'next-auth' {
  /**
   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {
    accessToken?: string
  }
}

Now you can do the following without getting an error:

// my-project/pages/api/auth/[...nextauth].ts

export const authOptions: AuthOptions = {
  // ...
  callbacks: {
    async jwt({ token, account }) {
      if (account) {
        token.id_token = account.id_token
        token.provider = account.provider
        token.accessToken = account.access_token
      }
      return token
    },
    async session({ session, token }) {
      session.accessToken = token.accessToken as string
      return session
    },
  }
  // ...
}

Note: you can also declare the module inside the [...nextauth].ts file.

Note 2: You may need to declare additional properties for the JWT interface:

// my-project/types/next-auth.d.ts

declare module 'next-auth/jwt' {
  interface JWT {
    id_token?: string
    provider?: string
    accessToken?: string
    // ... other properties you may need
  }
}
Accrual answered 14/5, 2023 at 23:58 Comment(1)
Thank you for the detailed answer. I'm still having an issue though. It seems my jwt callback is not getting called, and my Session object when I debug has only expired property, so my added accessToken does not seem to be applied. Do you know what could be a reason?Takashi
P
1

I ran into this problem recently (today), and came up with the following solution for my build, by extending the DefaultSession type from next-auth:

interface SessionExtension extends DefaultSession {
  accessToken: string;
  refreshToken: string;
}

Not sure if it will help your specific case, but hope it helps!

Edit: To elaborate and clarify

where I was seeing the error was in my apollo.ts file (I am using Apollo and graphql fed through a Rails backend). In this PR from October 9th, 2022, there are some changes made to the Default Session interface (packages/next-auth/src/core/types.ts:423) from export interface DefaultSession extends Record<string, unknown> {... to export interface DefaultSession {.... By removing the extension from Record<string, unknown> in the DefaultSession interface, it no longer allowed the DefaultSession to be extended in the way records allow, which means that the type definition that was given by the nextauth team could not be altered by us. So by changing our code to be

import { type DefaultSession } from 'next-auth';

...

interface SessionExtension extends DefaultSession {
  accessToken: string;
  apiToken: string;
  refreshToken: string;
}

...


const session = (await getSession()) as SessionExtension;

...

I manually changed the properties that the Default Session could allow, to let us access those other fields that we needed.

Pettway answered 11/4, 2023 at 16:9 Comment(1)
Can you please elaborate your solution? Its quite hard for new Next/NextAuth folks like me to understand this. If possible, please add some reference documentations.Malawi
O
0

I think that you have the problem in the jwt callback. Try to change the order of the props to:

async jwt({ token, account, user }) {
  // Initial sign in
  if (account && user) {
    return {
      accessToken: account.access_token,
      accessTokenExpires: Date.now() + account!.expires_at! * 1000,
      refreshToken: account.refresh_token,
      user,
    }
  }

  // Return previous token if the access token has not expired yet
  if (Date.now() < token.accessTokenExpires!) {
    return token
  }

  // Access token has expired, try to update it
  return refreshAccessToken(token)
}
Omen answered 14/3, 2023 at 13:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.