universal-router icon indicating copy to clipboard operation
universal-router copied to clipboard

redirect route? authorized routes

Open rstormsf opened this issue 9 years ago • 10 comments

How would you do this ? to make sure that the url is authorized

**Update:

Answer from @koistya on Gitter:

One way to do so is to have a route handler similar to this one const authorize = (state, next) => { if (!state.user) { state.redirect = '/login'; next(); } } and then in your routes file: on('/admin/users', authorize, (state) => <ManageUsers />) And add a handler in your render function which checks if state.redirect was set, and if so redirect user to the new location by using Location.pushState(null, state.redirect) on a client, and req.redirect(state.redirect) on server

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/26405822-redirect-route-authorized-routes?utm_campaign=plugin&utm_content=tracker%2F18115217&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F18115217&utm_medium=issues&utm_source=github).

rstormsf avatar Sep 03 '15 01:09 rstormsf

@rstormsf can you give me full example?

mychaelgo avatar Oct 30 '15 05:10 mychaelgo

A redirect example would be useful.

ChrisCinelli avatar Nov 02 '15 08:11 ChrisCinelli

This is roughly what I have. I'm not satisfied but it works for now.

router.js

const authorize = (state, next) => {
  if (!state.isAuthenticated) {
    state.redirect = '/login'; next();
  }
}

// If trying to access login page when Authenticated
const redirectAuth = (state, next) => {
  if (state.isAuthenticated) {
    state.redirect = '/';
  }
}

const router = new Router(on => {

  on('*', async (state, next) => {
    const component = await next();
    return component && <App context={state.context}>{component}</App>;
  });

  // Authenticated pages
  on('/editor', async (state, next) => {

    authorize(state, next);

    return (
      <EditorPage />
    )
  });

    // Public pages
  on('/login', async (state, next) => {

    redirectAuth(state, next);

    return (
      <LoginPage />
    )
  });
})

app.js

function run() {
  let currentLocation = null;
  let currentState = null;

  // Make taps on links and buttons work fast on mobiles
  //FastClick.attach(document.body);
  ReactTapEvent();

  // Check cookies if already logged in
  AppActions.isLoggedIn();

  let isAuthenticated = AppStore.isAuthenticated();

  // Get user data if authenticated
  if (isAuthenticated) {
    let userId = AppStore.getAuth().userId;
    AppActions.getUser(userId);
  }

  // Re-render the app when window.location changes
  const unlisten = Location.listen(location => {
    isAuthenticated = AppStore.isAuthenticated();

    currentLocation = location;
    currentState = Object.assign({}, location.state, {
      path: location.pathname,
      query: location.query,
      state: location.state,
      isAuthenticated: isAuthenticated,
      context
    });

    render(currentState);
  });

...

mfrye avatar Nov 04 '15 02:11 mfrye

@mfrye what is ReactTapEvent() ?

mychaelgo avatar Nov 05 '15 09:11 mychaelgo

@koistya is there any possibility now to redirect to different route? I see that post is quite old and universal-router API has changed since then?

pokorson avatar Jul 28 '16 09:07 pokorson

Admin route + redirect example:

client.js

import UniversalRouter from 'universal-router'
import { createHistory } from 'history'
import routes from './routes'

history = useQueries(createBrowserHistory())
history.listen(onLocationChange)
history.replace(history.getCurrentLocation()); // initial render

async function onLocationChange(location) {
  try {
    const routeActionResult = await UniversalRouter.resolve(routes, {
      path: location.pathname,
      store: { isAdmin: false },
      redirect(to) {
        const error = new Error(`Redirecting to "${to}"...`)
        error.status = 301
        error.path = to
        throw error
      }
    })

    syncOrAsyncClientSideRenderFn(routeActionResult)
  } catch (error) {
    if (error.status === 301) {
      history.replace(error.path || '')
      return
    }
    // log or display other errors here
  }
}

server.js

import express from 'express'
import UniversalRouter from 'universal-router'
import routes from './routes'

const server = express()
const port = process.env.PORT || 3000

app.get('*', async (req, res, next) => {
  try {
    const routeActionResult = await UniversalRouter.resolve(routes, {
      path: req.path,
      store: { isAdmin: false },
      redirect(to) {
        const error = new Error(`Redirecting to "${to}"...`)
        error.status = 301
        error.path = to
        throw error
      }
    })

    const html = syncOrAsyncServerSideRenderFn(routeActionResult)
    res.status(200).send(html)
  } catch(error) {
    if (error.status === 301) {
      req.redirect(status.path || '/')
      return
    }
    next(error)
  }
})

server.listen(port, () => {
  console.log(`Node.js app is running at http://localhost:${port}/`)
})

routes.js

export default [
  {
    path: '/',
    action() {
      return 'main page'
    }
  },
  {
    path: '/login',
    action() {
      return 'login page'
    }
  },
  {
    path: '/admin',
    action({ store, redirect }) {
      if (store.isAdmin) return 'admin page'
      return redirect('/login') // or return undefined to render 404 instead
    }
  }
]

frenzzy avatar Jul 28 '16 20:07 frenzzy

@frenzzy would be great to see such example in documentation :)

pokorson avatar Jul 31 '16 14:07 pokorson

@frenzzy It is needed do redirect through exception?

langpavel avatar Aug 02 '16 15:08 langpavel

@langpavel nope, it is just the easiest way to stop executing all routing code. In other way you need to handle redirect in several places (for example in each root/parent route) or add implementation to the core of router. But I do not see anything wrong with the exception approach, because we still need to handle errors such as 404 or 500.

frenzzy avatar Aug 02 '16 20:08 frenzzy

@frenzzy It seems that there is already a try catch block for route action in resolve function which take precedence over the try catch block above. For now, I set a boolean for redirection when required and use that to redirect later.

jayprakash1 avatar Sep 20 '16 16:09 jayprakash1