Reputation: 183
I'm struggling to pinpoint what the issue is with my code. Basically, I've set up a hooks.js file that checks and grabs the current user details if a user is authenticated and returns null otherwise. This hooks is supposed to be a helper function to then control access to pages (i.e. authenticated users who tried to access signin / register pages will be redirected to dashboard and vice versa).
However, it currently only works partially. For example, if I sign in and then try to access the signin / register page, it automatically redirects me to the dashboard page. However, if I'm logged out and I try to access the dashboard, I can access it and it won't redirect me to the signin page. Does anyone know what I'm doing wrong? I've tried to debug it by following the flow of the code (console logging in various places) and even within hooks.js, it seems like it should have executed the Router.push(redirectTo) code but it just doesn't seem to do it. Any help is appreciated (and snippets of the code included below)!
hooks.js
import { useEffect } from 'react';
import Router from 'next/router';
import useSWR from 'swr';
const fetcher = (url) =>
fetch(url)
.then((r) => r.json())
.then((data) => {
if (data?.user) {
return { user: data?.user };
} else {
return null;
}
});
export function useUser({ redirectTo, redirectIfFound } = {}) {
const { data, error } = useSWR('api/users/profile', fetcher);
const user = data?.user;
const finished = Boolean(data);
const hasUser = Boolean(user);
useEffect(() => {
if (!redirectTo || !finished) return;
if (
// If redirectTo is set, redirect if the user was not found.
(Boolean(redirectTo) && !redirectIfFound && !hasUser) ||
// If redirectIfFound is also set, redirect if the user was found
(redirectIfFound && hasUser)
) {
Router.push(redirectTo);
}
}, [redirectTo, redirectIfFound, finished, hasUser]);
return error ? null : user;
}
dashboard.js
import React from 'react';
import PrivateRouteLayout from '../components/privateRouteLayouts';
import { useUser } from '../lib/hooks';
function Dashboard() {
useUser({ redirectTo: '/signin', redirectIfFound: false });
return (
<PrivateRouteLayout title='Dashboard'>
<div>
<h1>Welcome !</h1>
</div>
</PrivateRouteLayout>
);
}
export default Dashboard;
signinform.js
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import Router from 'next/router';
import { useUser } from '../lib/hooks';
import validateSigninInput from '../lib/validation/login';
function SigninForm() {
useUser({ redirectTo: '/dashboard', redirectIfFound: true });
const [userDetails, setUserDetails] = useState({
email: '',
password: '',
});
const [error, setError] = useState({
email: '',
password: '',
});
function handleChange(event) {
setError({
email: '',
password: '',
});
setUserDetails({
...userDetails,
[event.target.name]: event.target.value,
});
}
async function handleSubmit(event) {
event.preventDefault();
const body = userDetails;
try {
const res = await fetch('/api/users/signin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (res.status === 200) {
Router.push('/dashboard');
} else if (res.status === 400) {
res.json().then((errMessage) => {
setError(errMessage);
});
}
} catch (error) {
console.error(error);
}
}
return (
<div className='valign-wrapper row auth-form-container'>
<div className='row'>
<div className='col s8 offset-s2'>
<Link href='/'>
<a>{'< Back to home page'}</a>
</Link>
</div>
<div className='input-field col s8 offset-s2'>
<label htmlFor='email'>Email</label>
<input
type='email'
onChange={handleChange}
name='email'
value={userDetails.email}
/>
<p className='error-message'> {error.email} </p>
</div>
<div className='input-field col s8 offset-s2'>
<label htmlFor='password'>Password</label>
<input
type='password'
onChange={handleChange}
name='password'
value={userDetails.password}
/>
<p className='error-message'>{error.password}</p>
</div>
<div className='col s8 offset-s2'>
<button
className='btn waves-effect waves-light submit-button'
onClick={handleSubmit}
>
Login
</button>
</div>
<div className='col s8 offset-s2 mt-10'>
<Link href='/register'>
<a>Don't have an account yet? Create one here</a>
</Link>
</div>
</div>
</div>
);
}
export default SigninForm;
Upvotes: 0
Views: 8566
Reputation: 4592
In my application router.push was being called twice in the same render cycle and neither had any effect. I don't know why, but after I removed one of them, router.push works as expected 🤷♂️ https://github.com/vercel/next.js/discussions/18522
Upvotes: 0
Reputation: 741
For this you will have to check from both server side and client side to check if user is authenticated or not for protected routes. Use authInitialProps
for protected pages.
dashboard.js
import React from 'react';
import PrivateRouteLayout from '../components/privateRouteLayouts';
import { useUser } from '../lib/hooks';
import { authInitialProps } from './auth';
function Dashboard() {
useUser({ redirectTo: '/signin', redirectIfFound: false });
return (
<PrivateRouteLayout title='Dashboard'>
<div>
<h1>Welcome !</h1>
</div>
</PrivateRouteLayout>
);
}
Dasboard.getInitialProps = authInitialProps(true);
export default Dashboard;
auth.js
const WINDOW_USER_SCRIPT_VAR = '__USER__';
// Server side function
export const getServerSideToken = (req) => {
const { signedCookies = {} } = req;
if (!signedCookies) {
return {};
} else if (!signedCookies.cookie) {
return {};
}
return { user: signedCookies.cookie };
};
export const getClientSideToken = () => {
if (typeof window !== 'undefined') {
const user = window[WINDOW_USER_SCRIPT_VAR] || {};
return { user };
}
return { user: {} };
};
/**
* Check for authenticated users
* @param {Boolean} isProtectedRoute
*/
export const authInitialProps = (isProtectedRoute) => ({ req, res }) => {
const auth = req ? getServerSideToken(req) : getClientSideToken();
const currentPath = req ? req.url : window.location.pathname;
const user = auth.user;
const isAnonymous = !user || user.type !== 'authenticated';
if (isProtectedRoute && isAnonymous && currentPath !== '/login') {
return redirectUser(res, `/login?redirect=${currentPath}`, currentPath);
}
return { auth };
};
Upvotes: 1