nextauthjs / next-auth

Authentication for the Web.
https://authjs.dev
ISC License
24.34k stars 3.4k forks source link

Bungie Provider and Typescript / Does this provider even work? #6930

Open BaileyMillerSSI opened 1 year ago

BaileyMillerSSI commented 1 year ago

Question 💬

I followed the example for adding the Bungie provider here. After doing so I found that the default implementation isn't happy with Typescript.

Argument of type '{ clientId: string; clientSecret: string; headers: { "X-API-Key": string; }; }' is not assignable to parameter of type 'Partial<OAuthConfig<any>>'. Object literal may only specify known properties, and 'headers' does not exist in type 'Partial<OAuthConfig<any>>'.

What is the intended setup when using Typescript with this provider?

How to reproduce ☕️

  1. Create a typescript application using create t3 app
  2. Select nextAuth and Typescript at least
  3. Go to the src/server/auth.ts file
  4. Locate the provider section
  5. Copy/Paste default provider implementation from provider docs

Contributing 🙌🏽

No, I am afraid I cannot help regarding this.

Unless it is as simple as changing the default to this:

BungieProvider({
      clientId: env.BUNGIE_CLIENT_ID,
      clientSecret: env.BUNGIE_SECRET,
      httpOptions: {
        headers: {
          "X-API-Key": env.BUNGIE_API_KEY
        }
      }
    }),
BaileyMillerSSI commented 1 year ago

Follow up question, does this provider even work?

Upon further investigation my short answer is no. Something is wrong deeper into the NextAuth logic then I can follow or configure. I've written a custom OAuth provider that gets through the token retrieval and then afterwards NextAuth tanks and I have no clue as to why. The same issue happens when using the default Bungie provider.

[next-auth][error][OAUTH_CALLBACK_ERROR] 
https://next-auth.js.org/errors#oauth_callback_error expected 200 OK, got: 500 Internal Server Error {
  error: OPError: expected 200 OK, got: 500 Internal Server Error
      at processResponse ({ProjectPath}node_modules\openid-client\lib\helpers\process_response.js:41:11)
      at Client.userinfo ({ProjectPath}node_modules\openid-client\lib\client.js:1237:18)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async oAuthCallback ({ProjectPath}node_modules\next-auth\core\lib\oauth\callback.js:131:17)
      at async Object.callback ({ProjectPath}node_modules\next-auth\core\routes\callback.js:52:11)
      at async AuthHandler ({ProjectPath}node_modules\next-auth\core\index.js:201:28)
      at async NextAuthHandler ({ProjectPath}node_modules\next-auth\next\index.js:24:19)
      at async {ProjectPath}node_modules\next-auth\next\index.js:60:32
      at async Object.apiResolver ({ProjectPath}node_modules\next\dist\server\api-utils\node.js:372:9)
      at async DevServer.runApi ({ProjectPath}node_modules\next\dist\server\next-server.js:514:9)
      at async Object.fn ({ProjectPath}node_modules\next\dist\server\next-server.js:828:35)
      at async Router.execute ({ProjectPath}node_modules\next\dist\server\router.js:243:32)
      at async DevServer.runImpl ({ProjectPath}node_modules\next\dist\server\base-server.js:432:29)
      at async DevServer.run ({ProjectPath}node_modules\next\dist\server\dev\next-dev-server.js:831:20)
      at async DevServer.handleRequestImpl ({ProjectPath}node_modules\next\dist\server\base-server.js:375:20)
      at async {ProjectPath}node_modules\next\dist\server\base-server.js:157:99 {
    name: 'OAuthCallbackError',
    code: undefined
  },
  providerId: 'bungie',
  message: 'expected 200 OK, got: 500 Internal Server Error'
benedfit commented 1 year ago

I have it working with v3.29.10, but have not got it working with v4

BaileyMillerSSI commented 1 year ago

Ah good so I'm not crazy it just doesn't work. I'll see about bumping my version down for the project I'm working on.

benedfit commented 1 year ago

I plan to put this in a PR at some point when I have some down time, but I managed to get it working in v4 with some additional config:

BungieProvider({
  clientId: process.env.BUNGIE_CLIENT_ID,
  clientSecret: process.env.BUNGIE_CLIENT_SECRET,
  // The Bungie API doesn't like scope being set
  authorization: { params: { scope: '' } },
  httpOptions: { headers: { 'X-API-Key': process.env.BUNGIE_API_KEY } },
  // Correctly gets the current user info so that the existing `profile` definition works
  userinfo: {
    async request({ tokens, provider }) {
      return await fetch(
        'https://www.bungie.net/platform/User/GetMembershipsForCurrentUser',
        {
          headers: {
            ...provider.httpOptions.headers,
            authorization: `Bearer ${tokens.access_token}`
          }
        }
      ).then(async response => await response.json())
    }
  }
})
BaileyMillerSSI commented 1 year ago

Awesome, I'll give that a try maybe this week. I am not familiar with this library but a better error message would have been very helpful too. It would be nice if the error said something along the lines of "Error loading user info from provider Bungie".

rrohans commented 1 year ago

Hey all, also using the BungieProvider with a new T3 application with the exact same setup as above. I'm running into the following issue:

[next-auth][error][OAUTH_CALLBACK_ERROR]
https://next-auth.js.org/errors#oauth_callback_error State cookie was missing. {
  error: TypeError: State cookie was missing.
      at Object.use (/Users/rhs_air/dev/d2builds/node_modules/next-auth/core/lib/oauth/checks.js:103:23)
      at oAuthCallback (/Users/rhs_air/dev/d2builds/node_modules/next-auth/core/lib/oauth/callback.js:89:25)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at async Object.callback (/Users/rhs_air/dev/d2builds/node_modules/next-auth/core/routes/callback.js:52:11)
      at async AuthHandler (/Users/rhs_air/dev/d2builds/node_modules/next-auth/core/index.js:201:28)
      at async NextAuthHandler (/Users/rhs_air/dev/d2builds/node_modules/next-auth/next/index.js:24:19)
      at async /Users/rhs_air/dev/d2builds/node_modules/next-auth/next/index.js:60:32
      at async Object.apiResolver (/Users/rhs_air/dev/d2builds/node_modules/next/dist/server/api-utils/node.js:372:9)
      at async DevServer.runApi (/Users/rhs_air/dev/d2builds/node_modules/next/dist/server/next-server.js:514:9)
      at async Object.fn (/Users/rhs_air/dev/d2builds/node_modules/next/dist/server/next-server.js:828:35)
      at async Router.execute (/Users/rhs_air/dev/d2builds/node_modules/next/dist/server/router.js:243:32)
      at async DevServer.runImpl (/Users/rhs_air/dev/d2builds/node_modules/next/dist/server/base-server.js:432:29)
      at async DevServer.run (/Users/rhs_air/dev/d2builds/node_modules/next/dist/server/dev/next-dev-server.js:831:20)
      at async DevServer.handleRequestImpl (/Users/rhs_air/dev/d2builds/node_modules/next/dist/server/base-server.js:375:20)
      at async /Users/rhs_air/dev/d2builds/node_modules/next/dist/server/base-server.js:157:99 {
    name: 'OAuthCallbackError',
    code: undefined
  },
  providerId: 'bungie',
  message: 'State cookie was missing.'
}

I check my browser's cookies and it seems that the state cookie is defined. Any idea what could be causing the issue?

BaileyMillerSSI commented 1 year ago

This is working for me:

interface IBungieNetUser {
  membershipId: number;
  uniqueName: string;
  displayName: string;
  profilePicture: number;
  profileTheme: number;
  userTitle: string;
  successMessageFlags: string | number;
  isDeleted: boolean;
  about: string;
  firstAccess: string;
  lastUpdate: string;
  context: {
    isFollowing: boolean;
    ignoreStatus: {
      isIgnored: boolean;
      ignoreFlags: boolean;
    };
  };
  xboxDisplayName: string;
  showActivity: boolean;
  locale: string;
  localeInheritDefault: boolean;
  showGroupMessaging: boolean;
  profilePicturePath: string;
  profileThemeName: string;
  userTitleDisplay: string;
  statusText: string;
  statusDate: string;
  blizzardDisplayName: string;
  steamDisplayName: string;
  twitchDisplayName: string;
  cachedBungieGlobalDisplayName: string;
  cachedBungieGlobalDisplayNameCode: number;
  egsDisplayName: string;
}

declare module "next-auth" {
  interface Session extends DefaultSession {
    user: {} & DefaultSession["user"] & IBungieNetUser;
  }
}

declare module "next-auth" {
  interface Profile {
    Response: {
      bungieNetUser: IBungieNetUser;
    };
  }
}

declare module "next-auth/jwt" {
  interface JWT {
    bungieUser: IBungieNetUser;
  }
}

export const authOptions: NextAuthOptions = {
  callbacks: {
    async jwt({ token, profile }) {
      if (profile) {
        token.bungieUser = profile.Response.bungieNetUser;
      }
      return token;
    },
    session({ session, token }) {
      if (token && session.user) {
        session.user = {
          ...session.user,
          ...token.bungieUser,
        };
      }
      return session;
    },
  },
  providers: [
    BungieProvider({
      clientId: env.BUNGIE_CLIENT_ID,
      clientSecret: env.BUNGIE_SECRET,
      // The Bungie API doesn't like scope being set
      authorization: { params: { scope: "" } },
      httpOptions: { headers: { "X-API-Key": env.BUNGIE_API_KEY } },
      // Correctly gets the current user info so that the existing `profile` definition works
      userinfo: {
        request: async ({ tokens, provider }) => {
          const fetchResult = await fetch(
            `${env.BUNGIE_API_ROOT}/Platform/User/GetMembershipsForCurrentUser/`,
            {
              headers: {
                ...provider?.httpOptions?.headers,
                authorization: `Bearer ${tokens.access_token}`,
              },
            }
          );

          return await fetchResult.json();
        },
      },
    }),
  ],
};

Index page:

const Home: NextPage = () => {
  const { data: sessionData } = useSession();

  return (
    <>
      <Head>
        <title>Create T3 App</title>
        <meta name="description" content="Generated by create-t3-app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
        {sessionData && (
          <div className="pb-5">
            <div className="flex flex-col items-center text-center">
              {sessionData?.user.image && (
                <Image
                  src={sessionData?.user.image}
                  unoptimized
                  width={320}
                  height={320}
                  alt="Profile picture"
                />
              )}
              <p className="text-xl text-white">{sessionData?.user.name}</p>
            </div>

            <pre className="text-white">
              {JSON.stringify(sessionData, null, "\t")}
            </pre>
          </div>
        )}
        <button
          className="rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20"
          onClick={
            sessionData ? () => void signOut() : () => void signIn("bungie")
          }
        >
          {sessionData ? "Sign out" : "Sign in"}
        </button>
      </main>
    </>
  );
};
rrohans commented 1 year ago

Nope, I still get the same error. FWIW, here is part of my package.json file for package versions.

"dependencies": {
    "@next-auth/prisma-adapter": "^1.0.5",
    "@prisma/client": "^4.9.0",
    "@tanstack/react-query": "^4.20.2",
    "@trpc/client": "^10.9.0",
    "@trpc/next": "^10.9.0",
    "@trpc/react-query": "^10.9.0",
    "@trpc/server": "^10.9.0",
    "next": "^13.2.1",
    "next-auth": "^4.19.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "superjson": "1.9.1",
    "zod": "^3.20.6"
  },
  "devDependencies": {
    "@types/eslint": "^8.21.1",
    "@types/node": "^18.14.0",
    "@types/prettier": "^2.7.2",
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@typescript-eslint/eslint-plugin": "^5.53.0",
    "@typescript-eslint/parser": "^5.53.0",
    "autoprefixer": "^10.4.7",
    "eslint": "^8.34.0",
    "eslint-config-next": "^13.2.1",
    "postcss": "^8.4.14",
    "prettier": "^2.8.1",
    "prettier-plugin-tailwindcss": "^0.2.1",
    "prisma": "^4.9.0",
    "tailwindcss": "^3.2.0",
    "typescript": "^4.9.5"
  },
  "ct3aMetadata": {
    "initVersion": "7.7.0"
  }
BaileyMillerSSI commented 1 year ago

Question for you, when you click sign in and get redirected to the Bungie screen do you see state in the url? Here is mine:

"dependencies": {
    "next": "^13.2.1",
    "next-auth": "^4.19.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "zod": "^3.20.6"
  },
  "devDependencies": {
    "@types/eslint": "^8.21.1",
    "@types/node": "^18.14.0",
    "@types/prettier": "^2.7.2",
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@typescript-eslint/eslint-plugin": "^5.53.0",
    "@typescript-eslint/parser": "^5.53.0",
    "autoprefixer": "^10.4.7",
    "eslint": "^8.34.0",
    "eslint-config-next": "^13.2.1",
    "postcss": "^8.4.14",
    "prettier": "^2.8.1",
    "prettier-plugin-tailwindcss": "^0.2.1",
    "tailwindcss": "^3.2.0",
    "typescript": "^4.9.5"
  },
  "ct3aMetadata": {
    "initVersion": "7.7.0"
  }
rrohans commented 1 year ago

Yes, I do, I think the URL is well-formed as per the docs. Seems like we're on the same versions for almost all of our dependencies.

BaileyMillerSSI commented 1 year ago

Here is my complete auth setup:

.env:

# Next Auth
# You can generate a new secret on the command line with:
# openssl rand -base64 32
# https://next-auth.js.org/configuration/options#secret
# NEXTAUTH_SECRET=""
NEXTAUTH_URL=https://localhost:3001 <--- This was important because of the proxy stuff. I am using a C# application to proxy my local development because of the Bungie API requiring even local host to be https.

# Next Auth Bungie Provider
BUNGIE_CLIENT_ID=
BUNGIE_SECRET=
BUNGIE_API_KEY=

# Links
BUNGIE_API_ROOT=https://www.bungie.net
import { type GetServerSidePropsContext } from "next";
import {
  getServerSession,
  type NextAuthOptions,
  type DefaultSession,
} from "next-auth";
import BungieProvider from "next-auth/providers/bungie";
import { env } from "~/env.mjs";

interface IBungieNetUser {
  membershipId: number;
  uniqueName: string;
  displayName: string;
  profilePicture: number;
  profileTheme: number;
  userTitle: string;
  successMessageFlags: string | number;
  isDeleted: boolean;
  about: string;
  firstAccess: string;
  lastUpdate: string;
  context: {
    isFollowing: boolean;
    ignoreStatus: {
      isIgnored: boolean;
      ignoreFlags: boolean;
    };
  };
  xboxDisplayName: string;
  showActivity: boolean;
  locale: string;
  localeInheritDefault: boolean;
  showGroupMessaging: boolean;
  profilePicturePath: string;
  profileThemeName: string;
  userTitleDisplay: string;
  statusText: string;
  statusDate: string;
  blizzardDisplayName: string;
  steamDisplayName: string;
  twitchDisplayName: string;
  cachedBungieGlobalDisplayName: string;
  cachedBungieGlobalDisplayNameCode: number;
  egsDisplayName: string;
}

declare module "next-auth" {
  interface Session extends DefaultSession {
    user: {} & DefaultSession["user"] & IBungieNetUser;
  }
}

declare module "next-auth" {
  interface Profile {
    Response: {
      bungieNetUser: IBungieNetUser;
    };
  }
}

declare module "next-auth/jwt" {
  interface JWT {
    bungieUser: IBungieNetUser;
  }
}

/**
 * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
 *
 * @see https://next-auth.js.org/configuration/options
 */
export const authOptions: NextAuthOptions = {
  callbacks: {
    async jwt({ token, profile }) {
      if (profile) {
        token.bungieUser = profile.Response.bungieNetUser;
      }
      return token;
    },
    session({ session, token }) {
      if (token && session.user) {
        session.user = {
          ...session.user,
          ...token.bungieUser,
        };
      }
      return session;
    },
  },
  providers: [
    BungieProvider({
      clientId: env.BUNGIE_CLIENT_ID,
      clientSecret: env.BUNGIE_SECRET,
      // The Bungie API doesn't like scope being set
      authorization: { params: { scope: "" } },
      httpOptions: { headers: { "X-API-Key": env.BUNGIE_API_KEY } },
      // Correctly gets the current user info so that the existing `profile` definition works
      userinfo: {
        request: async ({ tokens, provider }) => {
          const fetchResult = await fetch(
            `${env.BUNGIE_API_ROOT}/Platform/User/GetMembershipsForCurrentUser/`,
            {
              headers: {
                ...provider?.httpOptions?.headers,
                authorization: `Bearer ${tokens.access_token}`,
              },
            }
          );

          return await fetchResult.json();
        },
      },
    }),
  ],
};

/**
 * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
 *
 * @see https://next-auth.js.org/configuration/nextjs
 */
export const getServerAuthSession = (ctx: {
  req: GetServerSidePropsContext["req"];
  res: GetServerSidePropsContext["res"];
}) => {
  return getServerSession(ctx.req, ctx.res, authOptions);
};
rrohans commented 1 year ago

Alright, found my bug. My .env file had my NEXTAUTH_URL still using localhost rather than127.0.0.1. This would cause a redirect which would erase the cookie from the link between logins. Thanks for all your help!

BaileyMillerSSI commented 1 year ago

Alright, found my bug. My .env file had my NEXTAUTH_URL still using localhost rather than127.0.0.1. This would cause a redirect which would erase the cookie from the link between logins. Thanks for all your help!

I'm personally really surprised Bungie requires localhost to be secure which causes us to need to setup a cert and changing defaults. I'm so used to platforms letting localhost be open because it's used for development