Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[web-router] protected routes, how to implement them? #79

Open
einar-hjortdal opened this issue Jun 27, 2024 · 5 comments
Open

[web-router] protected routes, how to implement them? #79

einar-hjortdal opened this issue Jun 27, 2024 · 5 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@einar-hjortdal
Copy link

Web router expects factory functions in the component field, which means I cannot do

// ./routes.jsx
import AuthProvider from './components/AuthProvider'
const Dashboard = lazy(() => import('./pages/Dashboard'))
export const routes = [
  {
    path: '/',
    component: Root,
    children: [
      {
        path: '',
        component: <AuthProvider><Dashboard /></AuthProvider>
      },
      {
        path: 'login',
        component: lazy(() => import('./pages/Login'))
      },
      {
        path: 'not-found',
        component: lazy(() => import('./pages/NotFound'))
      },
      {
        path: '**',
        redirectTo: 'not-found'
      }
    ]
  }
]

I also cannot do

// ./pages/Dashboard.jsx
export default AuthProvider(Dashboard)

How does one implement "protected routes" with web-router?

@atellmer
Copy link
Owner

atellmer commented Jun 27, 2024

It might be worth using something like this

const protect = (factory: ComponentFactory) => {
  return component((props = {}) => {
    const { isLoggedIn } = useAuth();

    useLayoutEffect(() => {
      if (!isLoggedIn) {
        // redirect to login page
      }
    }, []);

    return isLoggedIn ? factory(props) : null;
  });
};

export const routes = [
  {
    path: '/',
    component: Root,
    children: [
      {
        path: '',
        component: protect(lazy(() => import('./pages/Dashboard'))),
      },
      {
        path: 'login',
        component: lazy(() => import('./pages/Login')),
      },
      {
        path: 'not-found',
        component: lazy(() => import('./pages/NotFound')),
      },
      {
        path: '**',
        redirectTo: 'not-found',
      },
    ],
  },
];

@atellmer atellmer added the question Further information is requested label Jun 27, 2024
@einar-hjortdal
Copy link
Author

Thank you for the lead. I think I got something working

const AuthContext = createContext(null)

export const useAuth = () => useContext(AuthContext)

// Wrap app
const AuthProvider = component(({ slot }) => {
  const api = useApi()
  const { isFetching, data, error } = useQuery('retrieveAdmin', api.retrieveAdmin)

  if (isFetching && !data) {
    return <div>Loading...</div>
  }

  return <AuthContext value={{ isFetching, data, error }}>{slot}</AuthContext>
})

export default AuthProvider

// Wrap pages that require auth
export const withAuth = (factory) => {
  return component((props) => {
    const { error } = useAuth()

    if (error) {
      const history = useHistory()
      history.push('/login')
      return null
    }

    return factory(props)
  })
}

// Login.jsx
const Login = component(() => {
  const { data } = useAuth()

  // redirect to dashboard if logged in
  if (data) {
    const history = useHistory()
    history.push('/')
    return null
  }

  return (
    <>
      login form
    </>
  )
})

@einar-hjortdal
Copy link
Author

einar-hjortdal commented Jun 28, 2024

I noticed that the code above doesn't actually work as expected with SSR.
I think history.push doesn't work right on the server, is that correct?
How is such a redirection supposed to be done on the server?

One possibility would be to only redirect on the browser, moving history.push in useEffect, but I would like the server to render the correct page.

@atellmer
Copy link
Owner

atellmer commented Jun 28, 2024

I think history.push doesn't work right on the server, is that correct?

Such actions are usually done through effects. In reality, history.push plans a new render, but it cannot be executed on the server.

One possibility would be to only redirect on the browser, moving history.push in useEffect

Sounds like a reasonable solution.

but I would like the server to render the correct page.

This would require the router to implement some method that would synchronously check the rights to the route when building the route tree. There is no such method in this implementation.
In addition, I can’t think of a use case where it would be necessary to redirect from a protected page to a login page only through the server. What's the benefit? This is a pretty narrow scenario for SSR.

@einar-hjortdal
Copy link
Author

This would require the router to implement some method that would synchronously check the rights to the route when building the route tree. There is no such method in this implementation. In addition, I can’t think of a use case where it would be necessary to redirect from a protected page to a login page only through the server. What's the benefit? This is a pretty narrow scenario for SSR.

Not only through server, but on first requests to the server in a isomorphic fashion. It's mostly an optimization to immediately deliver the login page html, for a faster perceived response time.
Not so important, so not going to pursue this now.

@atellmer atellmer added the enhancement New feature or request label Jun 30, 2024
@atellmer atellmer reopened this Jun 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants