フルスタックチャンネル
サインアップサインアップ
ログインログイン
利用規約プライバシーポリシーお問い合わせ
Copyright © All rights reserved | FullStackChannel
解決済
Django REST Framework + Next.jsでブログ構築 レッスン2 ログインエラー
DRF
Next.js
初心者
misha
2023/05/24 04:34

実現したいこと

Django REST Framework + Next.js + NextAuth.js(v4)でGoogle認証を行う

背景

上記のチュートリアルではNestAuth.jsのVersion 3が使用されているため、NextAuth公式サイトを参考にv4に書き換えながらチュートリアルを進めています。

発生している問題

Googleログイン画面への移行には成功し、アカウントを選択したところで"Access Denied, You do not have permission to sign in" と表示されます。

ソースコード

[...nextauth.js]

import NextAuth from "next-auth"
import axios from "axios"
import GoogleProvider from "next-auth/providers/google"

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
<!-- ここ以降はチュートリアルとコードは同じ -->
callbacks: {
    async signIn(user, account, profile) {
      if (account.provider === 'google') {
        const { accessToken, idToken } = account

        try {
          const response = await axios.post(
            `${process.env.DJANGO_URL}/api/social/login/google/`,
            {
              access_token: accessToken,
              id_token: idToken,
            }
          )

          const { access_token } = response.data
          user.accessToken = access_token

          return true
        } catch (error) {
          return false
        }
      }
      return false
    },
    async jwt(token, user, account, profile, isNewUser) {
      if (user) {
        const { accessToken } = user

        token.accessToken = accessToken
      }

      return token
    },
    async session(session, user) {
      session.accessToken = user.accessToken
      return session
    },
  }
}

export default (req, res) => NextAuth(req, res, settings)

自分で試したこと

1. DjangoかNextどちらに問題があるか確認

はる先生のgithubからフロントエンドのプログラムをダウンロードし、私がチュートリアルを参考に(完全なコピーで変更箇所はない)したDjangoと動かしてみました。→Googleログインに成功したため、Djangoのエラーはなし、next.jsの方に問題があることがわかりました。

2. Nextのエラーの詳細を確認

[.../nextauth].jsのcallbacks-のasync signIn...の中のtry-catch文に、エラーがある場合エラーを出力するように追記し、再度自分で作成したプログラムを動かし同じエラーを再現したところ、
next側のターミナルで

AxiosError: Request failed with status code 400
    at settle (file:///Users/myuser/Documents/projects/djangonext-googleauth/next/node_modules/axios/lib/core/settle.js:19:12)
    at IncomingMessage.handleStreamEnd (file:///Users/myuser/Documents/projects/djangonext-googleauth/next/node_modules/axios/lib/adapters/http.js:495:11)
    at IncomingMessage.emit (node:events:525:35)
    at endReadableNT (node:internal/streams/readable:1358:12)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {

...以下省略(エラーの一部を最後に載せています)

そしてDjang側のターミナル上で

Bad Request: /api/social/login/google/
[04/Nov/2022 20:17:48] "POST /api/social/login/google/ HTTP/1.1" 400 75

が出ていました。

callbacks内の設定に不備があるようです。

3. [../nextauth].jsにconsole.logを足す

 async signIn(user, account, profile) {
      if (account.provider === 'google') {
        const { accessToken, idToken } = account
        console.log("\naccessToken"+accessToken + "\nidToken:"+idToken )

おそらくdjangoへのpostでエラーが発生しているため、その直前で定義している変数を出力する設定を行い、変数が正しく定義されているか確認を行いました。

accessToken:undefined
idToken:undefined

公式ドキュメントによると、accountはプロバイダのことを指しています。

https://next-auth.js.org/configuration/events#events

よって、

const { accessToken, idToken } = account

でgoogleからaccessTokenとidTokenの取得と変数の作成に失敗しているのではないか?というところまで推測できましたが、これ以上はわかりませんでした。

このエラーを解決するために、何をやればいいか見当がつかない
状況です。
手順やプロセスなど、何かアドバイスがあればお伺いしたいです。
長文になってしまいましたが、よろしくお願いいたします。


AxiosError: Request failed with status code 400
    at settle (file:///Users/iamuser/Documents/projects/djangonext-googleauth/next/node_modules/axios/lib/core/settle.js:19:12)
    at IncomingMessage.handleStreamEnd (file:///Users/iamuser/Documents/projects/djangonext-googleauth/next/node_modules/axios/lib/adapters/http.js:495:11)
    at IncomingMessage.emit (node:events:525:35)
    at endReadableNT (node:internal/streams/readable:1358:12)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  code: 'ERR_BAD_REQUEST',
  config: {
    transitional: {
      silentJSONParsing: true,
      forcedJSONParsing: true,
      clarifyTimeoutError: false
    },
    adapter: [Function: httpAdapter],
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    env: { FormData: [Function], Blob: null },
    validateStatus: [Function: validateStatus],
    headers: AxiosHeaders {
      'Content-Type': 'application/json',
      'User-Agent': 'axios/1.1.3',
      'Content-Length': '2',
      'Accept-Encoding': 'gzip, deflate, br',
      [Symbol(defaults)]: [Object]
    },
    method: 'post',
    url: 'http://127.0.0.1:8000/api/social/login/google/',
    data: '{}'
  },

.....
    config: {
      transitional: [Object],
      adapter: [Function: httpAdapter],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      maxBodyLength: -1,
      env: [Object],
      validateStatus: [Function: validateStatus],
      headers: [AxiosHeaders],
      method: 'post',
      url: 'http://127.0.0.1:8000/api/social/login/google/',
      data: '{}'
    },



      method: 'POST',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      path: '/api/social/login/google/',
      ....
回答 5件
login
回答するにはログインが必要です
nekomusume
2年以上前

私もドハマりしました。
下記のコードで動きました。
(next-auth v4.15.1)

はる先生違っていましたら訂正お願いいたします。

pages/api/api/[...nextauth].js

import NextAuth from 'next-auth'
import axios from 'axios'
import GoogleProvider from "next-auth/providers/google"

const settings = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  secret: process.env.SECRET,


  callbacks: {
    async signIn(user, account, profile) {

      console.log(user.account)

      if (user.account.provider === 'google') {

        const accessToken = user.account.access_token
        const idToken = user.account.id_token

        try {
          const response = await axios.post(
            `${process.env.DJANGO_URL}/api/social/login/google/`,
            {
              access_token: accessToken,
              id_token: idToken,
            }
          )

          const { access_token } = response.data
          user.accessToken = access_token

          return true
        } catch (error) {

          return false
        }
      }
      return false
    },


    async jwt({ token, account }) {

      if (account) {
        token.accessToken = account.access_token
      }
      return token
    },

    async session({ session, token, user }) {

      session.accessToken = token.accessToken
      return session
    }
    
  },
}

export default (req, res) => NextAuth(req, res, settings)

念のためこちらも載せます。

components/layout/navigation.js


import { signIn, signOut, useSession } from 'next-auth/react';
import Link from "next/link";

export default function Navigation() {

  const { data: session } = useSession()

  return (
    <header className="container flex flex-row items-center mx-auto px-5 py-14 max-w-screen-lg">
      <Link legacyBehavior href="/">
        <a className="text-4xl font-bold text-red-300">NextJS Startup</a>
      </Link>
      <nav className="ml-auto">
        <Link legacyBehavior href="/about">
          <a className="mr-5">About</a>
        </Link>
        {session && session.accessToken ? (
          <>
            <Link legacyBehavior href="/profile">
              <a className="mr-5">Profile</a>
            </Link>
            <div
              className="inline-block cursor-pointer"
              onClick={() => signOut()}
            >
              Logout
            </div>
          </>
        ) : (
          <>
            <div
              className="inline-block cursor-pointer"
              onClick={() => signIn()}
            >
              Login
            </div>
          </>
        )}
      </nav>
    </header>
  );
}


pages/_app.js


import '../styles/globals.css'
import Head from 'next/head'
import Layout from '../components/layout/layout'
import { SessionProvider } from "next-auth/react"

function MyApp({ Component, pageProps:{ session, ...pageProps }, }) {
  return (
    <SessionProvider session={pageProps.session}>
      <Layout>
        <Head>
          <meta name="viewport" content="width=device-width, initial-scale=1" />
        </Head>
        <Component {...pageProps} />
      </Layout>
    </SessionProvider>
  )
}

export default MyApp


1
misha
2年以上前

@nekomusume さん、コードの共有ありがとうございました。
ご自身の開発中にこのエラーに遭遇した時に参考にしたウェブサイトや読むべきドキュメントなど、お時間ある時にURLだけでも結構ですので共有していただけないでしょうか。

私の方では、例えばaccount.providerがなぜuser.account.providerとするかなど、わからない箇所がある状況です。

お手数おかけいたしますが、よろしくお願いいたします。

1
misha
2年以上前

ありがとうございます!
解決いたしました。

1
はる@講師
2年以上前

nekomusumeさん

コードありがとうございます。

助かります。

はる@講師
2年以上前

NestAuthをVersion4にして、こちらでも試してみます。

2