Strange issue with NextJS and NextAuth

huangapple go评论54阅读模式
英文:

Strange issue with NextJS and NextAuth

问题

以下是您要翻译的代码部分:

async function refreshAccessToken(authToken: AuthToken) {
    try {
        const tokenResponse = await AuthApi.refreshToken(authToken.refresh_token);
        return new AuthToken(
            tokenResponse.access_token,
            tokenResponse.token_type,
            tokenResponse.expires_in,
            tokenResponse.refresh_token
        );

    } catch (error) {
        return new AuthToken(
            authToken?.access_token,
            authToken?.token_type,
            authToken?.expires_in,
            authToken?.refresh_token,
            "刷新令牌时发生错误"
        );
    }
}

export const authOptions: NextAuthOptions = {
    providers: [
        CredentialsProvider({
            name: 'credentials',
            credentials: {
                username: { label: '用户名', type: 'text' },
                password: { label: '密码', type: 'password' }
            },
            authorize: async (credentials) => {
                try {
                    if (!credentials) {
                        return null;
                    }

                    const { username, password } = credentials;
                    const authToken = await AuthApi.login(username, password);

                    const session: Session = new Session(authToken, null);
                    if (authToken != null && authToken.error == '') {
                        const userDetails = await AuthApi.getUserDetails(authToken.access_token);
                        if (userDetails != null) {
                            session.user = userDetails;
                        }
                    }
                    console.log(session);
                    return session as any;

                } catch (error) {
                    console.error(error);
                    throw error;
                }
            }
        })
    ],
    session: {
        strategy: 'jwt',
        maxAge: Constants.AccessTokenLifetimeSeconds
    },
    secret: process.env.APP_SECRET,
    jwt: {
        secret: process.env.APP_SECRET,
        maxAge: Constants.AccessTokenLifetimeSeconds
    },
    pages: { signIn: '/login' },
    callbacks: {
        jwt: async ({ user }: any) => {

            let token: AuthToken | null = null;

            if (user != null && user.token != null) {

                token = user.token;

                const clock = new Clock();
                if (user.token.expiry_date_time < clock.nowUtc()) {
                    token = await refreshAccessToken(user.token);
                }
            }
            console.log("OO", user, token);
            return token;
        },
        session: async ({ session, user, token }) => {
            //session.user = user;
            //session.token = token;
            return session;
        }
    }
};
export default NextAuth(authOptions);

希望这能帮助您解决问题。如果您有任何其他问题或需要进一步的帮助,请告诉我。

英文:

I have the following auth setup for NextJS using a custom token provider/service. I am following this guide and my code looks like

async function refreshAccessToken(authToken: AuthToken) {
try {
const tokenResponse = await AuthApi.refreshToken(authToken.refresh_token);
return new AuthToken(
tokenResponse.access_token,
tokenResponse.token_type,
tokenResponse.expires_in,
tokenResponse.refresh_token
);
} catch (error) {
return new AuthToken(
authToken?.access_token,
authToken?.token_type,
authToken?.expires_in,
authToken?.refresh_token,
&quot;An error occurred whilst refreshing token&quot;
);
}
}
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: &#39;credentials&#39;,
credentials: {
username: { label: &#39;Username&#39;, type: &#39;text&#39; },
password: { label: &#39;Password&#39;, type: &#39;password&#39; }
},
authorize: async (credentials) =&gt; {
try {
if (!credentials) {
return null;
}
const { username, password } = credentials;
const authToken = await AuthApi.login(username, password);
const session: Session = new Session(authToken, null);
if (authToken != null &amp;&amp; authToken.error == &#39;&#39;) {
const userDetails = await AuthApi.getUserDetails(authToken.access_token);
if (userDetails != null) {
session.user = userDetails;
}
}
console.log(session);
return session as any;
} catch (error) {
console.error(error);
throw error;
}
}
})
],
session: {
strategy: &#39;jwt&#39;,
maxAge: Constants.AccessTokenLifetimeSeconds
},
secret: process.env.APP_SECRET,
jwt: {
secret: process.env.APP_SECRET,
maxAge: Constants.AccessTokenLifetimeSeconds
},
pages: { signIn: &#39;/login&#39; },
callbacks: {
jwt: async ({ user }: any) =&gt; {
let token: AuthToken | null = null;
if (user != null &amp;&amp; user.token != null) {
token = user.token;
const clock = new Clock();
if (user.token.expiry_date_time &lt; clock.nowUtc()) {
token = await refreshAccessToken(user.token);
}
}
console.log(&quot;OO&quot;, user, token);
return token;
},
session: async ({ session, user, token }) =&gt; {
//session.user = user;
//session.token = token;
return session;
}
}
};
export default NextAuth(authOptions);

The relevent classes are

export default class AuthToken implements IAuthToken {
public expiry_date_time: Date;
constructor(
public access_token: string,
public token_type: string,
public expires_in: number,
public refresh_token: string,
public error: string = &quot;&quot;
) {
const clock = new Clock();
this.expiry_date_time = new Date(
clock.nowUtc().getTime() + (this.expires_in - 60) * 1000
);
}
}

and

export default class Session implements ISession {
constructor(
public token: AuthToken | null,
public user: UserDetails | null
) { }
}

But in the callback, I am getting the following error

>error - TypeError: JWT Claims Set MUST be an object
at new ProduceJWT (C:\VRPM\Repos\portal\node_modules\jose\dist\node\cjs\jwt\produce.js:10:19)
at new EncryptJWT (C:\VRPM\Repos\portal\node_modules\jose\dist\node\cjs\jwt\encrypt.js:7:1)
at Object.encode (C:\VRPM\Repos\portal\node_modules\next-auth\jwt\index.js:49:16)
at async Object.callback (C:\VRPM\Repos\portal\node_modules\next-auth\core\routes\callback.js:429:22)
at async NextAuthHandler (C:\VRPM\Repos\portal\node_modules\next-auth\core\index.js:295:28)
at async NextAuthNextHandler (C:\VRPM\Repos\portal\node_modules\next-auth\next\index.js:23:19)
at async C:\VRPM\Repos\portal\node_modules\next-auth\next\index.js:59:32
at async Object.apiResolver (C:\VRPM\Repos\portal\node_modules\next\dist\server\api-utils\node.js:184:9)
at async DevServer.runApi (C:\VRPM\Repos\portal\node_modules\next\dist\server\next-server.js:403:9)
at async Object.fn (C:\VRPM\Repos\portal\node_modules\next\dist\server\base-server.js:493:37) {
page: '/api/auth/[...nextauth]'

and I have no idea how to resolve as the data I am returning is an object:

 token: AuthToken {
access_token: &#39;...Nn_ooqUtFvdfh53k&#39;,
token_type: &#39;Bearer&#39;,
expires_in: 86400,
refresh_token: &#39;...ZddCpNTc&#39;,
error: &#39;&#39;,
expiry_date_time: 2023-02-09T19:29:15.307Z
}

Can anyone tell me where I am going wrong here?


I have found that if I return

return { 
token: token
};

the error goes away, but what is the right way to do this?

答案1

得分: 2

以下是翻译好的内容:

One thing I noticed in all examples of next-auth implementations is that the authorize() function is meant to return the user's details (id, email, etc), not an object containing a token & user details.

我在所有的next-auth实现示例中注意到一件事,那就是authorize()函数应该返回用户的详细信息(如id、电子邮件等),而不是包含tokenuser详细信息的对象。

It seems like you are incorrectly relying on the token from this user object under the jwt() callback when it should be derived from the callback itself.

看起来你正在错误地依赖于jwt()回调中的用户对象中的令牌,而它应该是从回调本身派生的。

I've setup a Next.js + next-auth sandbox and I'm stepping through your code now.

我已经设置了一个Next.js + next-auth的沙盒,现在我正在逐步查看你的代码。

Instead of this:

不要使用以下代码:

Do this:

改用以下代码:

Also inside authorize():

还在authorize()函数中:

Change this from:

将此更改从:

return session;

改成:

return session.user;

Important change in the jwt() callback:

jwt()回调中的重要更改

jwt: async ({ user }: any) => {

jwt: async ({ token, user }: any) => {

Additionally, your session() callback should not be returning the refreshToken. They mention this security concern in their docs.

另外,你的session()回调不应该返回refreshToken。他们在他们的文档中提到了这个安全问题。

The session() callback should be updated.

session()回调应该更新为:

From this:

从这个:

session: async ({ session, user, token }) => {
//session.user = user;
//session.token = token;
return session; <--- entire token (refreshToken, etc) being returned...
}

To this:

改成这个:

session: async ({ session, user, token }) => {
// Send properties to the client, like an access_token and user from a provider.
session.accessToken = token.accessToken;
session.user = user;
return session
}
英文:

One thing I noticed in all examples of next-auth implementations is that the authorize() function is meant to return the user's details (id, email, etc), not an object containing a token & user details.

It seems like you are incorrectly relying on the token from this user object under the jwt() callback when it should be derived from the callback itself.

I've setup a Next.js + next-auth sandbox and I'm stepping through your code now.

Instead of this:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

jwt: async ({ user }: any) =&gt; {
let token = user.token;
if (user != null &amp;&amp; user.token != null) {
const clock = new Clock();
if (user.token.expiry_date_time &lt; clock.nowUtc()) {
token = await refreshAccessToken(user.token);
}
}
return token;
},

<!-- end snippet -->

Do this:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

jwt: async ({ token, user }: any) =&gt; {
// at login, save user token to jwt token
if (user) {
token.accessToken = user.token.accessToken;
token.accessTokenExpiry = user.token.accessTokenExpiry;
token.refreshToken = user.token.refreshToken;
}
// check if we should refresh the token
const shouldRefreshTime = user.token.expiry_date_time &lt; clock.nowUtc();
if (shouldRefreshTime) {
token = refreshAccessToken(token);
}
// return the token
return Promise.resolve(token);
},

<!-- end snippet -->

Also inside authorize():

Change this from:

return session;

To:

return session.user;

Important change in the jwt() callback:

jwt: async ({ user }: any) =&gt; {

To:

jwt: async ({ token, user }: any) =&gt; {

Additionally, your session() callback should not be returning the refreshToken. They mention this security concern in their docs.

The session() callback should be updated.

From this:

session: async ({ session, user, token }) =&gt; {
//session.user = user;
//session.token = token;
return session; &lt;--- entire token (refreshToken, etc) being returned...
}

To this:

session: async ({ session, user, token }) =&gt; {
// Send properties to the client, like an access_token and user from a provider.
session.accessToken = token.accessToken;
session.user = user;
return session
}

huangapple
  • 本文由 发表于 2023年2月9日 03:37:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/75390923.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定