I’m building a dashboard using Next, Auth js and the Spotify API. I’ve implemented the OAuth flow and the user connects with the Spotify account correctly.
Spotify after the authentication process gives you an access token and a refresh token, because the access token expires after 1 hour.
You can use the refresh token to get another access token and I would like to do that automatically, but for some reasons it doesn’t work.
This is what I did in auth.ts, basically I followed the documentation.
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;
},
},
});
Right now when I’m logged in, after one hour, if I refresh the page it will give me a fetch error(I’m fetching some data to display in the main page of the dasboard). It happens because the access token was not refreshed correctly. If I refresh the error page again, then it will work and display the data correctly.
What I want to achieve is refreshing the token automatically, without having errors, so the user can stay on the main page as long as he wants and refresh the data whenever he wants.
This is how I’m getting the access token in the page and use it to fetch:
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 ?? "";
Thank you!
Eduard is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.