Server Side Auth with Next.js, Context and Hooks
Use React Context to access auth data server side with Next.js.
When using authentication in a Next.js app you want to have a seamless
experience. Doing user authentication server side can prevent unnecessary
spinners within your app. We can create server side rendered authentication in
Next.js using React Context in an _app.js
file. By wrapping our entire
application in an auth context, we can validate users server side and have
access to the current user within any component.
This method should work for any backend authentication solution you have. The process goes as followed:
- Create an
AuthContext
provider to contain your authentication logic and provide methods for dealing with losing in/out and getting user info - Wrap your application with our
Auth.Provider
in_app.js
. - Use the
useContext
hook to get user data from within your components.
Auth Context
First create a context for us to store authentication data. React Context gives us access to Provider and Consumer components to set and retrieve data anywhere within our app.
import axios from 'axios';
import { createContext } from 'react';
import router from 'next/router';
const AuthContext = createContext();
export const getUser = async (ctx) => {
return await axios
.get(`${process.env.NEXT_PUBLIC_API_URL}/token`, {
headers: ctx.?req?.headers?.cookie ? { cookie: ctx.req.headers.cookie } : undefined,
withCredentials: true,
})
.then((response) => {
if (response.data) {
return { status: 'SIGNED_IN', user: response.data };
} else {
return { status: 'SIGNED_OUT', user: null };
}
})
.catch((error) => {
return { status: 'SIGNED_OUT', user: null };
});
};
export const AuthProvider = (props) => {
const auth = props.myAuth || { status: 'SIGNED_OUT', user: null };
const login = async (email, password) => {
// Use any auth service methods here
return await axios({
method: 'post',
url: `${process.env.NEXT_PUBLIC_API_URL}/login`,
data: { email, password },
withCredentials: true,
})
.then(() => {
router.push('/');
console.log('user signed in');
})
.catch((error) => {
console.error('Incorrect email or password entered.);
});
};
const register = async (email, password) => {
return await axios({
method: 'post',
url: `${process.env.NEXT_PUBLIC_API_URL}/register`,
data: { email, password },
withCredentials: true,
})
.then(function (response) {
router.push('/');
console.log('user registered');
})
.catch(function (error) {
console.error(error.message);
});
};
const logout = async () => {
return await axios
.get(`${process.env.NEXT_PUBLIC_API_URL}/logout`, { withCredentials: true })
.then(() => {
router.push('/');
console.log('user logged out');
})
.catch((error) => {
console.error(error.message);
});
};
return <AuthContext.Provider value={{ auth, logout, register, login }} {...props} />;
};
export const useAuth = () => React.useContext(AuthContext);
export const AuthConsumer = AuthContext.Consumer;
We now have a Context Provider with all the methods needed for handling user authentication.
Wrapping application with _app.js
Create a _app.js
in the pages directory. This allows us to have access the
root of our react app. Here we can wrap our whole application with our Auth
Provider.
We will use getInitialProps
to make a call to our /token
endpoint to check
if a user exists with our current cookies. If there is no valid session user
will be set to null
and status will be SIGNED_OUT
import App from "next/app"
import { AuthProvider, getUser } from "../components/contexts/AuthContext"
const MyApp = ({ Component, pageProps, auth }) => {
return (
<AuthProvider myAuth={auth}>
<Component {...pageProps} />
</AuthProvider>
)
}
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext)
const auth = await getUser(appContext.ctx)
return { ...appProps, auth: auth }
}
export default MyApp
Use useContext()
hook to access AuthContext data
Now we can access our auth data and methods anywhere within our app. The
useContext
hook makes this especially simple.
// Navigation.jsx
function Navigation() {
const { auth } = useAuth()
if (auth.status === "SIGNED_OUT") {
return <LoggedOut />
}
if (auth.status === "SIGNED_IN") {
return <LoggedIn user={auth.user} />
}
}
Login/Register
We have access to login
and regsiter
methods from our context.
const LoginPage = () => {
const { login, register } = useAuth()
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleLogin = async (e) => {
e.preventDefault()
// Log in or Register here
login(email, password)
// register(email, password)
}
return (
<form onSubmit={handleLogin}>
<Text as={"h1"} fontSize={28} fontWeight={"bold"}>
Log in
</Text>
<FormControl>
<FormLabel htmlFor="email">Email</FormLabel>
<Input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</FormControl>
<FormControl>
<FormLabel htmlFor="email">Password</FormLabel>
<Input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</FormControl>
<Button type={"submit"}>Log In </Button>
</form>
)
}
Log out
Logging out is just as easy. We have access to our logout
method.
const LoggedIn = () => {
const { logout } = useAuth()
return <Button onClick={logout}>Logout</Button>
}
By using React Context in our Next.js app we can get access to user auth not only in the client but server side too.
Have some feedback on this post? .