In my NextJS authentication setup, I am using a custom token provider/service as outlined in this guide. The code structure is as follows:
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,
"An error occurred whilst refreshing token"
);
}
}
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: 'credentials',
credentials: {
username: { label: 'Username', type: 'text' },
password: { label: 'Password', 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);
The relevant 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 = ""
) {
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
) { }
}
However, when handling the callback, I encounter 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]'
I am returning an object, and yet the error persists:
token: AuthToken {
access_token: '...Nn_ooqUtFvdfh53k',
token_type: 'Bearer',
expires_in: 86400,
refresh_token: '...ZddCpNTc',
error: '',
expiry_date_time: 2023-02-09T19:29:15.307Z
}
If I modify it to
return {
token: token
};
the error disappears. Can someone shed light on the correct approach here?