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) |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
@jordymeow @josiahwiebe have you guys seen this discussion ? this might be relevant - #10724 |
Beta Was this translation helpful? Give feedback.
-
Hey @jordymeow, any solution with getServerSideProps in a HOC for authentication?? |
Beta Was this translation helpful? Give feedback.
-
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). |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Hi all, same on my side. Any update ? :) |
Beta Was this translation helpful? Give feedback.
-
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.
|
Beta Was this translation helpful? Give feedback.
-
do not use functional component in _app.js. |
Beta Was this translation helpful? Give feedback.
-
i found this https://github.com/piglovesyou/nextjs-passport-oauth-example/blob/9d7119368d5624bd638d3e83fc26b9f3a0bdbfa5/lib/withIdentity.tsx#L23 |
Beta Was this translation helpful? Give feedback.
-
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 withAuthentication Then, 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) |
Beta Was this translation helpful? Give feedback.
-
I went down the rabbit hole on this one trying to update the |
Beta Was this translation helpful? Give feedback.
-
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')); |
Beta Was this translation helpful? Give feedback.
-
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... |
Beta Was this translation helpful? Give feedback.
-
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? |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
Opened a RFC for redirecting here: #14890 |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
How about using firebase proposed way of handling user sessions From their example repo:
|
Beta Was this translation helpful? Give feedback.
-
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 } |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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 :)
Beta Was this translation helpful? Give feedback.
All reactions