Repository navigation
Replies: 22 comments 47 replies
|
I'm currently testing an implementation something like this. Would actually love some feedback on it because it might be a terrible idea, but it does seem to work for me. I didn't use
import Router from 'next/router'
import { useState, useEffect, createContext } from 'react'
import cookie from 'js-cookie'
import { trigger, mutate } from 'swr'
export const UserContext = createContext()
const App = ({ Component, pageProps }) => {
const [user, setUser] = useState()
const [token, setToken] = useState()
// get the token from the cookie
const cookieToken = cookie.get('token')
// login function to be called on a login page
const login = ({ user, token }) => {
// save the token from the login response in a cookie
cookie.set('token', token, { expires: 14 })
// save the userId from the login response in a cookie
cookie.set('userId', user.id, { expires: 14 })
setUser(user)
setToken(token)
trigger(`/api/users/${user.id}`)
}
const logout = () => {
setUser(null)
cookie.remove(token)
// invalidate the user with swr
mutate(`/api/users/${user.id}`, {}, false)
Router.push('/login')
}
useEffect(() => {
if (cookieToken) setToken(cookieToken)
}, [])
return (
<UserContext.Provider
value={{
user: user,
token: cookieToken || token,
login: login,
logout: logout,
setUser: setUser,
}}>
<Component {...pageProps} />
</UserContext.Provider>
)
}
export default App
import { useEffect, useState } from 'react'
import cookie from 'js-cookie'
import useSWR from 'swr'
import Router from 'next/router'
import { useAuth } from '../hooks/useAuth'
function getRedirectTo() {
if (typeof window !== 'undefined' && window.location) {
return window.location
}
return {}
}
export const withUser = WrappedComponent => {
const Wrapper = props => {
const { setUser } = useAuth()
const [token, setToken] = useState()
const [userId, setUserId] = useState()
const [shouldFetchUser, setShouldFetchUser] = useState(false)
const cookieUserId = cookie.get('userId')
const cookieToken = cookie.get('token')
const fetchWithToken = (url, token) => {
return fetch(url, {
method: 'GET',
headers: { 'x-access-token': token },
}).then(res => res.json())
}
const { data: fetchedUser } = useSWR(shouldFetchUser ? [`/api/users/${userId}`, token] : null, fetchWithToken)
useEffect(() => {
const redir = getRedirectTo()
if (cookieToken && cookieUserId) {
setToken(cookieToken)
setUserId(cookieUserId)
setShouldGetUser(true)
} else {
Router.replace(`/login?r=${redir.pathname + encodeURIComponent(redir.search)}`, '/login', { shallow: true })
}
}, [shouldGetUser])
useEffect(() => {
if (fetchedUser) {
setUser(fetchedUser)
setRoles(fetchedUser.roles)
}
}, [fetchedUser])
return <WrappedComponent {...props} />
}
return Wrapper
}
import { useContext } from 'react'
import { UserContext } from '../pages/_app'
export const useAuth = () => {
const { user, token, login, logout, setUser } = useContext(UserContext)
return { user, token, login, logout, setUser }
}Now to implement it on an actual page, I wrap the page with the
import { withUser } from '../hocs/withUser'
import { useAuth } from '../hooks/useAuth'
const Profile = () => {
const { user } = useAuth()
return user && (
<div>
<h1>Hello, {user.name}</h1>
</div>
)
}
export default withUser(Profile) |
|
I am using something similar, with a global context. It works quite well for me. Here it is: The getStatus is actually a function that performs a request on the API on my app. This API returns the account of the connected user. The FuncComponent, in that case, wakes up and updates the global state through an action. |
|
@jordymeow @josiahwiebe have you guys seen this discussion ? this might be relevant - #10724 |
Hey @jordymeow, any solution with getServerSideProps in a HOC for authentication?? |
|
Hi @saeedPadyab, sorry, I could not make it work with getServerSideProps for some reason. I am keeping an eye on the updated examples of the NextJS repository but I haven't seen them doing it yet. They are always using getServerSideProps as an export on a page (which is magically picked by the framework). |
|
I can confirm that migrating examples of HOC from getInitialProps to getStatic**** does not work in my attempts. Using React Context in similar ways mentioned in this thread is my current approach too. |
|
Hi all, same on my side. Any update ? :) |
|
Having the same issue here. Assuming since the function is not exported the framework magic passes it over. I'm using a (very) hacky workaround for now until I can figure the right method. |
|
do not use functional component in _app.js. |
|
i found this https://github.com/piglovesyou/nextjs-passport-oauth-example/blob/9d7119368d5624bd638d3e83fc26b9f3a0bdbfa5/lib/withIdentity.tsx#L23 |
|
I don't know if it is a solution, but I am doing this way: withAuthentication.js const withAuthentication = WrappedComponent => props => {
const { user } = useAuth()
if (user) return <WrappedComponent {...props} />
return <p>Unauthorized</p>
}
withAuthentication.isAuth = async ctx => {
try{
await axios.get('/api/me', { ctx }) // I use 'ctx' in the interceptors
return true
} catch(error) {
return false
}
}
export default withAuthenticationThen, in the page file: pages/dashboard.js const Dashboard = ({ data }) => { ... }
export async function getServerSideProps(ctx) {
if (!(await withAuthentication.isAuth(ctx))) return { props: {} }
// ...
}
export default withAuthentication(Dashboard) |
|
I went down the rabbit hole on this one trying to update the |
|
I don't think you'll be able to use I'm assuming that you're using We can get all of that behavior with something like this: export const getServerSideProps = (ctx) => {
// I'm waving my hand here because how you determine whether the user
// is authenticated is not the topic of discussion and it varies widely
if (!isAuthenticated(context)) {
ctx.res.writeHead(302, { Location: '/login' });
ctx.res.end();
return;
}
return {
props: await getData('/api/data')
}
}We don't want to add that code to every page where we request data so we can easily abstract it into a helper method: function ensureAuth(ctx) {
if (!isAuthenticated(context)) {
ctx.res.writeHead(302, { Location: '/login' });
ctx.res.end();
return;
}
}
export const getServerSideProps = (ctx) => {
ensureAuth(ctx);
return {
props: await getData('/api/data')
}
}Or a pattern that is more like an HOC: function ensureAuth(gssp) {
return async (ctx) => {
if (!isAuthenticated(context)) {
ctx.res.writeHead(302, { Location: '/login' });
ctx.res.end();
return;
}
return gssp(ctx);
}
}
function getApiData(url) {
return async (ctx) => {
return {
props: await getData(url)
}
}
}
export const getServerSideProps = ensureAuth(getApiData('/api/data')); |
|
I think the with-iron-Session and in there the static generation approach is a very good one for replace a HOC. But it seems that I missed something... |
|
Ok I think I got! This is expanding on @justincy's idea. Update: Make sure to split the two functions into two separate files in order for the code elimination to work. // withAuthServerSide.tsx
import React from "react";
export function withAuthServerSideProps(getServerSidePropsFunc?: Function){
return async (context: any) => {
const user = await getUser(context);
if(getServerSidePropsFunc){
return {props: {user, data: await getServerSidePropsFunc(context, user)}};
}
return {props: {user, data: {props: {user}}}};
}
}
async function getUser(content: any) {
return {
id: 1,
username: "JBis",
email: "test@test.com"
}
}
// withAuthComponent.tsx
export function withAuthComponent(Component: any){
return ({user, data}:{user: any, data: any}) => {
if(!user){
return <h1>Denied</h1> // or redirect, we can use the Router because we are client side here
}
return <Component {...data.props}/>
}
}And then implement it: // protected.tsx
import {withAuthComponent} from '../client/hoc/withAuthComponent';
import {withAuthServerSideProps} from '../client/hoc/withAuthServerSide';
function protectedPage({user}: {user: any}) {
return <h1>Hello {user.username}</h1>
}
export default withAuthComponent(protectedPage)
export const getServerSideProps = withAuthServerSideProps();Clean. Simple. Thoughts? |
|
For anyone following this topic of authentication in Next.js. I spent a lot of time digging the subject of authentication in Next.js and ended up implementing almost exactly what the vercel.com/dashboard is doing: that's next-iron-session (https://github.com/vvo/next-iron-session/). The Next.js example from the repo will show you how to use SWR to authenticate clients from the frontend and automatically redirecting when people are not logged in. Though one thing that is not shown in the example is how to have a "global user state". Because I think this is not the "SWR way", which is to avoid having to use any complex state handling library and just have either HOCs around components forwarding you the user or just go with requesting the user on every page you need it. SWR will deduplicate automatically, so your code becomes clearer: you never wonder where does this user comes from: you asked for it via const user = useUser(); About "Not the right way to do redirects" this only applies to the server side generated example of next-iron-session. In most cases you want to stick with static generation and redirects using JavaScript only (like vercel.com/dashboard). If you're concerned about the redirects being "not done well", for now this is a Next.js limitation and the tracking issue is: #12409 |
|
For anyone following this topic I wrote a custom import { GetServerSidePropsContext } from 'next'
type AsyncReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => Promise<infer U>
? U
: T extends (...args: any) => infer U
? U
: any
export type InferWithAuthServerSideProps<
T extends (...args: any) => Promise<{ props: any }>
> = AsyncReturnType<T>['props']
type WithAuthServerSidePropsOptions = {
authenticatedPage?: boolean
}
export type AuthenticatedPageProps = {
user: User
}
type EmptyProps = {
props: Record<string, unknown>
}
type DefaultWithAuthServerSideProps = {
user: User
}
function withAuthServerSideProps<T extends EmptyProps = EmptyProps>(
getServerSidePropsFunc?: (
ctx: GetServerSidePropsContext,
user?: User,
) => Promise<T>,
options: WithAuthServerSidePropsOptions = {},
) {
return async function getMergedServerSideProps(
ctx: GetServerSidePropsContext,
): Promise<{ props: T['props'] & DefaultWithAuthServerSideProps }> {
let loggedInUser: User | null = null
try {
loggedInUser = await getLoggedInUser(ctx.req.headers.cookie || '')
} catch {
loggedInUser = null
}
if (options.authenticatedPage && !loggedInUser) {
return ({
redirect: {
destination: '/sign-in',
permanent: false,
},
// We have to trick the TS compiler here.
} as unknown) as { props: T['props'] & DefaultWithAuthServerSideProps }
}
if (getServerSidePropsFunc) {
return {
props: {
user: loggedInUser,
...((await getServerSidePropsFunc(ctx, loggedInUser)).props || {}),
},
}
}
return {
props: {
user: loggedInUser,
},
}
}
}
export { withAuthServerSideProps }I'm by no means a typescript expert so it would be nice if we didn't have to trick the TS compiler when returning a redirect instead of props but I couldn't figure out how to do it. And here's how you would use it: import {
InferWithAuthServerSideProps,
withAuthServerSideProps,
} from '../utils/with-auth-server-side-props'
export const getServerSideProps = withAuthServerSideProps(async (_ctx, user) => {
const posts = await getPosts(user.id)
return {
props: {
posts
}
}
}, {
authenticatedPage: true,
})
type AppProps = InferWithAuthServerSideProps<typeof getServerSideProps>
function App({ user, posts }: AppProps) {
return <p>Hello {user.username} π, you have {posts.length} posts.</p>
}
export default App |
|
How about using firebase proposed way of handling user sessions From their example repo:
|
|
I faced the same problem & found a simple solution (using import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '@/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))And then I use it like: export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})My import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr } |
|
Hi! I tried my hand at a POC repo that brings this all together with the latest Next & Typescript versions (using NextAuth.js): https://github.com/timfee/nextauthjs-server-side-props-wrapper |

Uh oh!
There was an error while loading. Please reload this page.
Hello,
There are examples of how to perform authentification using cookies for one page (like the example with-cookie-auth), however, in the real world, when we use auth we also use it on many different pages.
I tried to play with the _app to catch getServiceSideProps (but that's not possible) or even the getInitialProps (never get called) with today's release of NextJS, but can't find any solution.
Ideally, I would love to do this in a HOC. I would use it only for my pages requiring authentification. That HOC would also update my global state. My problem is that I can find a way to make a HOC on which the getServiceSideProps get called. Is there a way?
Thanks a lot :)
All reactions