我正在使用 Next、Auth js 和 Spotify API 构建一个仪表板。我已经实现了 OAuth 流程,并且用户正确连接了 Spotify 帐户。 身份验证过程后,Spotify 会为您提供访问令牌和刷新令牌,因为访问令牌会在 1 小时后过期。 您可以使用刷新令牌来获取另一个访问令牌,我希望自动执行此操作,但由于某些原因它不起作用。
这就是我在 auth.ts 中所做的,基本上我遵循文档。
import NextAuth, { DefaultSession } from "next-auth";
import Spotify from "next-auth/providers/spotify";
declare module "next-auth" {
interface Session extends DefaultSession {
access_token?: string;
expires_at?: number;
refresh_token?: string;
error?: "RefreshAccessTokenError";
}
}
declare module "@auth/core/jwt" {
interface JWT {
access_token: string;
expires_at: number;
refresh_token: string;
error?: "RefreshAccessTokenError";
user?: DefaultSession["user"];
}
}
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Spotify({
clientId: process.env.AUTH_SPOTIFY_ID,
clientSecret: process.env.AUTH_SPOTIFY_SECRET,
authorization: `https://accounts.spotify.com/authorize?scope=user-read-private user-read-email user-top-read user-read-recently-played user-library-read`,
}),
],
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user; // !! is used here to convert the potentially truthy/falsy value of auth?.user to a strict boolean representation
const isOnDashboard = nextUrl.pathname.startsWith("/dashboard");
if (isOnDashboard) {
if (isLoggedIn) return true;
return false; // redirect authenticated users to login page
} else if (isLoggedIn) {
return Response.redirect(new URL("/dashboard", nextUrl));
}
return true;
},
async jwt({ token, account, user }) {
if (account) {
console.log("New authentication, creating new token");
return {
...token,
access_token: account.access_token,
expires_at: Math.floor(
Date.now() / 1000 + (account.expires_in || 3600)
),
refresh_token: account.refresh_token,
user,
};
} else if (Date.now() < token.expires_at * 1000) {
console.log("token still valid, returning existing token");
return token;
} else {
console.log("token expired, trying to refresh");
if (!token.refresh_token) throw new Error("Missing refresh token");
try {
const response = await fetch(
"https://accounts.spotify.com/api/token",
{
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.AUTH_SPOTIFY_ID!,
client_secret: process.env.AUTH_SPOTIFY_SECRET!,
grant_type: "refresh_token",
refresh_token: token.refresh_token,
}),
method: "POST",
}
);
const tokens = await response.json();
if (!response.ok) throw tokens;
console.log("token refreshed successfully, returning updated token");
return {
...token,
access_token: tokens.access_token,
expires_at: Math.floor(
Date.now() / 1000 + (tokens.expires_in || 3600)
),
refresh_token: tokens.refresh_token || token.refresh_token,
};
} catch (error) {
console.error("Error refreshing access token", error);
return { ...token, error: "RefreshAccessTokenError" as const };
}
}
},
session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
session.access_token = token.access_token as string;
}
return session;
},
},
});
现在,当我登录时,一小时后,如果我刷新页面,它会给我一个获取错误(我正在获取一些数据以显示在仪表板的主页中)。发生这种情况是因为访问令牌未正确刷新。如果我再次刷新错误页面,它将正常工作并正确显示数据。
我想要实现的是自动刷新令牌,并且不会出现错误,这样用户就可以在主页上停留多久,并随时刷新数据。
这就是我在页面中获取访问令牌并使用它来获取的方式:
export default async function Page() {
const session = await auth();
if (!session?.user) return null;
console.log("Session:", session); // Log the session object
console.log("Access Token:", session.access_token); // Log the access token
const accessToken = session.access_token ?? "";
谢谢!
澄清一下: 获取新令牌是否存在问题,或者在获取数据时访问它是否存在问题?
你明白了吗
console.log("token refreshed successfully, returning updated token")
,
或者你得到
console.error("Error refreshing access token", error);
?
--
我刚刚检查了我的代码,因为我目前也在使用 Spotify api,我注意到的第一件事是你的计算方式
expires_at
。
我跳过 account.expires_at 的计算步骤,直接执行
expires_at: account.expires_at
要检查令牌是否仍然有效,我只需将其与当前时间进行比较:
async jwt({token, account}) {
if(account) {
return {
...token,
accessToken: account.access_token
refreshToken: account.refresh_token,
expires_at: account.expires_at
};
}
if (token.expires_at && Date.now() < token.expires_at) {
console.log("USING_OLD_TOKEN";
return token;
}
const refreshedToken = await refreshAccessToken(token);
return refreshedToken;
}
我只对引用的令牌进行计算,因为它不包括
expires_at
,而只包括 expires_in
。
async function refreshAccessToken(token) {
//removed try catch for readability
const res = await fetch(...)
const refreshedToken = await res.json();
return {
...token,
accessToken: refreshedToken.access_token,
expires_at: Date.now() + refreshedToken.expires_in * 1000,
refreshToken: refreshedToken.refresh_token
}
}