使用 next-auth 凭据提供程序时如何发送 httponly cookie 客户端?

Sau*_*lla 20 next.js next-auth

我正在创建一个 next js 应用程序,使用 next-auth 来处理身份验证。

我有一个外部后端 api,所以我使用 Credentials Provider。

问题是后端发送 httponly cookie,但当我在客户端发出请求时,这些 cookie 没有附加到浏览器。

在 /pages/api/[...auth].js 中

import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import clientAxios from '../../../config/configAxios'

export default NextAuth({
    providers: [
        Providers.Credentials({
            async authorize(credentials) {
                try {
                    const login = await clientAxios.post('/api/login', {
                        username: credentials.username,
                        password: credentials.password,
                        is_master: credentials.is_master
                    })


                    const info = login.data.data.user
                    const token = {
                        accessToken: login.data.data.access_token,
                        expiresIn: login.data.data.expires_in,
                        refreshToken: login.data.data.refresh_token
                    }
                    // I can see cookies here
                    const cookies = login.headers['set-cookie']

                    return { info, token, cookies }
                } catch (error) {
                    console.log(error)
                    throw (Error(error.response.data.M))
                }
            }
        })
    ],
    callbacks: {
        async jwt(token, user, account, profile, isNewUser) {
            if (token) {
               // Here cookies are set but only in server side
               clientAxios.defaults.headers.common['Cookie'] = token.cookies
            }
            if (user) {
                token = {
                    user: user.info,
                    ...user.token,
                }
            }

            return token
        },
        async session(session, token) {
            // Add property to session, like an access_token from a provider.
            session.user = token.user
            session.accessToken = token.accessToken
            session.refreshToken = token.refreshToken

            return session
        }
    },
    session: {
        jwt: true
    }
})
Run Code Online (Sandbox Code Playgroud)

我的 axios 配置文件

import axios from 'axios';

const clientAxios = axios.create({

    baseURL: process.env.backendURL,
    withCredentials: true,
    headers:{
        'Accept' : 'application/json',
        'Content-Type' : 'application/json'
    }

});

export default clientAxios;
Run Code Online (Sandbox Code Playgroud)

页面组件

import { getSession } from "next-auth/client";
import clientAxios from "../../../config/configAxios";
import { useEffect } from "react"

export default function PageOne (props) {
    useEffect(async () => {
      // This request fails, cookies are not sent
      const response = await clientAxios.get('/api/info');
    }, [])

    return (
        <div>
           <h1>Hello World!</h1>
        </div>
    )
}

export async function getServerSideProps (context) {
    const session = await getSession(context)

    if (!session) {
        return {
            redirect: {
                destination: '/login',
                permanent: false
            }
        }
    }

    // This request works
    const response = await clientAxios.get('/api/info');
    
    return {
        props: {
            session,
            info: response.data
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

Sau*_*lla 28

经过一段时间的研究,我已经弄清楚了。

我必须以导出 NextAuth 的方式对 /pages/api/auth 进行更改。

代替

export default NextAuth({
    providers: [
       ...
    ]

})

Run Code Online (Sandbox Code Playgroud)

像这样导出它,这样我们就可以访问请求和响应对象

export default (req, res) => {
    return NextAuth(req, res, options)
}
Run Code Online (Sandbox Code Playgroud)

但是要在选项对象中访问它们,我们可以将其设为回调

const nextAuthOptions = (req, res) => {
    return {
        providers: [
           ...
        ]
    }
}

export default (req, res) => {
    return NextAuth(req, res, nextAuthOptions(req, res))
}
Run Code Online (Sandbox Code Playgroud)

要将 cookie 从后端发送回前端,我们必须在响应中添加“Set-Cookie”标头

res.setHeader('Set-Cookie', ['cookie_name=cookie_value'])
Run Code Online (Sandbox Code Playgroud)

完整的代码是

import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';

const nextAuthOptions = (req, res) => {
    return {
        providers: [
           CredentialsProvider({
                async authorize(credentials) {
                   try {                      
                        const response = await axios.post('/api/login', {
                            username: credentials.username,
                            password: credentials.password
                        })

                        const cookies = response.headers['set-cookie']

                        res.setHeader('Set-Cookie', cookies)
                        
                        return response.data
                    } catch (error) {
                        console.log(error)
                        throw (Error(error.response))
                    } 
                }
           })
        ]
    }
}

export default (req, res) => {
    return NextAuth(req, res, nextAuthOptions(req, res))
}
Run Code Online (Sandbox Code Playgroud)

更新 - 打字稿示例

为回调创建类型 nextAuthOptions

import { NextApiRequest, NextApiResponse } from 'next';
import { NextAuthOptions } from 'next-auth';

type NextAuthOptionsCallback = (req: NextApiRequest, res: NextApiResponse) => NextAuthOptions
Run Code Online (Sandbox Code Playgroud)

结合一切

import { NextApiRequest, NextApiResponse } from 'next';
import NextAuth, { NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import axios from 'axios'

type NextAuthOptionsCallback = (req: NextApiRequest, res: NextApiResponse) => NextAuthOptions

const nextAuthOptions: NextAuthOptionsCallback = (req, res) => {
     return {
        providers: [
           CredentialsProvider({
                credentials: {
                },
                async authorize(credentials) {
                   try {                      
                        const response = await axios.post('/api/login', {
                            username: credentials.username,
                            password: credentials.password
                        })

                        const cookies = response.headers['set-cookie']

                        res.setHeader('Set-Cookie', cookies)

                        return response.data
                    } catch (error) {
                        console.log(error)
                        throw (Error(error.response))
                    } 
                }
           })
        ],
        callbacks: {
            ...
        },
        session: {
            ...
        }
    }
}

export default (req: NextApiRequest, res: NextApiResponse) => {
    return NextAuth(req, res, nextAuthOptions(req, res))
}
Run Code Online (Sandbox Code Playgroud)

  • @ScreamoIsDead 我用打字稿的示例更新了答案 (2认同)