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)
はる先生のgithubからフロントエンドのプログラムをダウンロードし、私がチュートリアルを参考に(完全なコピーで変更箇所はない)したDjangoと動かしてみました。→Googleログインに成功したため、Djangoのエラーはなし、next.jsの方に問題があることがわかりました。
[.../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内の設定に不備があるようです。
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/',
....
私もドハマりしました。
下記のコードで動きました。
(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
@nekomusume さん、コードの共有ありがとうございました。
ご自身の開発中にこのエラーに遭遇した時に参考にしたウェブサイトや読むべきドキュメントなど、お時間ある時にURLだけでも結構ですので共有していただけないでしょうか。
私の方では、例えばaccount.providerがなぜuser.account.providerとするかなど、わからない箇所がある状況です。
お手数おかけいたしますが、よろしくお願いいたします。
ありがとうございます!
解決いたしました。
nekomusumeさん
コードありがとうございます。
助かります。
NestAuthをVersion4にして、こちらでも試してみます。