Building upon the insights provided by @Nijat, I found a solid foundation to work with.
If you are utilizing Next-Auth, here is my approach to integrating their exposed functionalities. The key components here are the middleware and the use of decode function that leverages jose.jwtVerify. To completely eliminate jsonwebtoken, I made modifications to the encode function to utilize jose.SignJWT instead.
src/middleware.ts:
import { NextRequestWithAuth, withAuth } from 'next-auth/middleware';
import { NextFetchEvent } from 'next/server';
import jwtConfig from './config/jwt';
export default withAuth(
function middleware(request: NextRequestWithAuth, event: NextFetchEvent) {},
{
jwt: {
decode: jwtConfig.decode,
},
callbacks: {
authorized: ({ req, token }) => {
console.log(token);
return !!token;
},
},
}
);
export const config = {
matcher: [
'/((?!api/auth|auth|static|_next/static|_next/image|images|favicon.ico|health).*)', // blocking all api routes except /api/auth
],
};
src/config/jwt.ts
import { JWT, JWTDecodeParams, JWTEncodeParams, getToken } from 'next-auth/jwt';
import * as jose from 'jose';
const encodedSecret = new TextEncoder().encode(process.env.NEXTAUTH_SECRET);
const encode = async (params: JWTEncodeParams): Promise<string> => {
const signedToken = await new jose.SignJWT(params.token)
.setProtectedHeader({ alg: 'HS256' })
.sign(encodedSecret);
if (!signedToken) {
throw new Error('Failed to sign token');
}
return signedToken;
};
const decode = async (params: JWTDecodeParams): Promise<JWT | null> => {
if (!params.token) {
throw new Error('Failed to verify token');
}
let token = params.token;
if (params.token.startsWith('Bearer')) {
token = params.token.replace('Bearer ', '');
}
try {
const decoded = await jose.jwtVerify(token, jwtConfig.encodedSecret);
if (!decoded.payload) {
throw new Error('Failed to verify token');
}
return decoded.payload;
} catch (error) {
console.log(error);
throw new Error(`${error}`);
}
};
export const jwtConfig = {
encode,
decode,
};
export default jwtConfig;
Finally, in src/pages/api/auth/[...nextauth].ts:
import { JWT, JWTDecodeParams, JWTEncodeParams, getToken } from 'next-auth/jwt';
import * as jose from 'jose';
const encodedSecret = new TextEncoder().encode(process.env.NEXTAUTH_SECRET);
const encode = async (params: JWTEncodeParams): Promise<string> => {
const signedToken = await new jose.SignJWT(params.token)
.setProtectedHeader({ alg: 'HS256' })
.sign(encodedSecret);
if (!signedToken) {
throw new Error('Failed to sign token');
}
return signedToken;
};
const decode = async (params: JWTDecodeParams): Promise<JWT | null> => {
if (!params.token) {
throw new Error('Failed to verify token');
}
let token = params.token;
if (params.token.startsWith('Bearer')) {
token = params.token.replace('Bearer ', '');
}
try {
const decoded = await jose.jwtVerify(token, jwtConfig.encodedSecret);
if (!decoded.payload) {
throw new Error('Failed to verify token');
}
return decoded.payload;
} catch (error) {
console.log(error);
throw new Error(`${error}`);
}
};
export const jwtConfig = {
encode,
decode,
encodedSecret,
};
export default jwtConfig;