I'm using NextAuth with NEXT.js app router for authentication.
The dashboard component is wrapped in auth-guard which checks whether the user is signed in or not. the auth-guard's code is as follows.
export default function AuthGuard({ children }: Props) {
const { loading } = useAuthContext();
return <>{loading ? <SplashScreen /> :
<SeshProviders>
<Container>
{children}
</Container>
</SeshProviders>
}</>;
}
// ----------------------------------------------------------------------
function Container({ children }: Props) {
const router = useRouter();
const { authenticated, method } = useAuthContext();
const { data: session, status } = useSession();
const [checked, setChecked] = useState(false);
console.log('session from auth-guard', session);
const check = useCallback(() => {
if (status !== 'authenticated') {
const searchParams = new URLSearchParams({
returnTo: window.location.pathname,
}).toString();
const loginPath = loginPaths[method];
const href = `${loginPath}?${searchParams}`;
console.log('taking back from auth guard', session);
router.replace(href);
} else {
setChecked(true);
console.log('auth-guard said authenticated');
}
}, [router, status, session]);
useEffect(() => {
check();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!checked) {
return null;
}
return <>{children}</>;
}
My next auth config:
// nextauth/auth.ts
export const authOptions: NextAuthOptions = {
// Configure one or more authentication providers
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials, req) {
console.log(credentials);
if (!credentials) return null;
const email = credentials.email;
const password = credentials.password;
const res = await axios.post(endpoints.auth.login, { email, password });
if (res.status === 200) {
const { token, user } = res.data;
user.token = token;
return user;
}
return null;
},
}),
],
secret: process.env.NEXAUTH_SECRET,
callbacks: {
async redirect({ url, baseUrl }: { url: string; baseUrl: string }) {
if (!url) return '/dashboard/user';
return url;
},
async jwt({ token, user }: { token: any; user: any }) {
if (user) {
token.accessToken = user.token;
}
console.log('jwt', token);
return token;
},
async session({ session, token, user }: { session: any; token: any; user: any }) {
// Send properties to the client, like an access_token and user id from a provider.
session.accessToken = token.accessToken;
session.user.id = token.id;
console.log('session', session);
return session;
},
},
};
export const getServerAuthSession = () => getServerSession(authOptions);
also app/api/auth/[...nextauth]/route.ts
import { authOptions } from 'src/nextauth/nextauth';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Now when I login, the router takes me to the dashboard for a second and then takes my back to login. console's result
Any idea what I might be doing wrong? (or if I'm doing anything right)
It is better to use a middleware to handle protected pages. This will also prevent the page from loading until the session is loaded.
If you really must do this client-side, you have to check that the status is not "loading"
. The useSession
hook makes a request to the auth API provided by next-auth, which will load the session information.
Your useEffect
should also depend on check
, since it can change based on your session state. Suppressing that warning is a bug.