Server-Side Auth Flow with React Router - reactjs

I was wondering how to exactly implement authentication flow with React Router if my app is rendering server-side?
The scenario:
When the user first arrives at the app, we invoke an onEnter router method in order to check if any current user is present (localStorage), if they are we will redirect them to their dashboard, otherwise we redirect them to the landing page.
Therefore when my app hits my protected routes onEnter, here's ideally what should happen:
onEnter: () => {
if (localStorage.getItem('authToken')) {
// Authenticate and redirect user to intended path
} else {
// Redirect the user to the login page
}
}
The problem here is that because we're rendering server-side, we don't have access to things such as localStorage. Can anyone suggest a way to overcome this, or a better way to handle server-side auth with React Router?

You could use context, e.g:
import React, { Component, PropTypes, Children } from 'react'
class Provider extends Component {
static childContextTypes = {
isAuthed: PropTypes.bool
}
constructor(props) {
super(props);
this.initialData = isClient ? window.__initialData__ : props.initialData;
}
getChildContext() {
return {
isAuthed: this.initialData.isAuthed
};
}
render() {
let { children } = this.props;
return Children.only(children);
}
}
And wrap:
// The server creates this object:
const initialData = {
isAuthed: true
};
if (IS_CLIENT) {
ReactDOM.render(
<Provider>
<Router history={createHistory()}>{Routes}</Router>
</Provider>,
document.getElementById('app')
);
}
if (IS_SERVER) {
// Using renderToStaticMarkup/renderToString etc on:
<Provider initialData={initialData}>
<RoutingContext {...renderProps} />
</Provider>
}
You can then access it on the server or client from any component:
import React, { Component, PropTypes } from 'react'
class SomeComponent extends Component {
static contextTypes = {
isAuthed: PropTypes.bool
}
constructor(props, context) {
super(props, context);
console.log(context.isAuthed); // this.context outside ctor
}
}
Simple client check something like typeof document !== 'undefined'

Related

Higher Order Components and Authenticate Routes

I am creating Higher Order Components in React and making sure that user cannot access the protected routes. Everything works fine but I am unsure where to put the code for checking the redux state. Currently, in the code below I have placed all the code in componentDidMount. This is because componentWillMount is deprecated. Is this the best place to check because componentDidMount is triggered after the component has been mounted.
import React, { Component } from 'react';
import { connect } from 'react-redux'
export default function(ComposedComponent) {
class Authenticate extends Component {
componentDidMount() {
if(!this.props.isAuthenticated) {
this.props.history.push('/')
}
}
render() {
return (
<ComposedComponent {...this.props} />
)
}
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.isAuthenticated
}
}
return connect(mapStateToProps)(Authenticate)
}
Assuming the correct state is available at construction, you can do the redirect in the constructor:
class Authenticate extends Component {
constructor(props) {
super(props);
if(!props.isAuthenticated) {
props.history.push('/')
}
}
...
}
This is the purpose of React Router Redirect component:
render() {
return !this.props.isAuthenticated ? (
<Redirect to="/" />
) : (
<ComposedComponent {...this.props} />
)
}

Redirect React Router from Context Provider

I'm new to React Router and trying to do a redirect from inside a provider using the new Conext API. basically my provider looks like this.
/* AuthContext.js */
class AuthProvider extends React.Component {
state = { isLoggedIn: false }
constructor() {
super()
this.login = this.login.bind(this)
this.logout = this.logout.bind(this)
}
login() {
this.setState({ isLoggedIn: true })
// Need to redirect to Dashboard Here
}
logout() {
this.setState({ isLoggedIn: false })
}
render() {
return (
<AuthContext.Provider
value={{
isLoggedIn: this.state.isLoggedIn,
login: this.login,
logout: this.logout
}}
>
{this.props.children}
</AuthContext.Provider>
)
}
}
const AuthConsumer = AuthContext.Consumer
export { AuthProvider, AuthConsumer }
I've read a lot about how to pass the history object using props and how to use a component but I can't see how these approaches would work here. My context provider sits at the top of the tree so it's not a child of the Router so I can't pass props. It's also not a standard component so I can't just insert a component, unless I've misunderstood something (which is very possible).
Edit: Looks like the way to go is withRouter, but how to export my AuthProvider in the code above so that history.push is available in my login function? As you can see I'm exporting multiple components wrapped in {} so can you wrap one of these in a HOC and do you have to explicitly pass history in or is it always available inside the component that's being wrapped?
use withRouter, sth like this to get access of history.
const AuthButton = withRouter( ({ history }) =>history.push("/"));
Try This:
import { Route } from "react-router-dom";
class AuthProvider extends React.Component {
yourFunction = () => {
doSomeAsyncAction(() =>
this.props.history.push('/dashboard')
)
}
render() {
return (
<div>
<Form onSubmit={ this.yourFunction } />
</div>
)
}
}
export default withRouter(AuthProvider);
Best explanation can be found here: Programmatically navigate using react router

React router v4 route onchange event

Is there any way to fire an event when route changes with react router v4. I need to fire a function on every route change. I use BrowserRouter and Switch from react-router-dom on client side in universal react-redux application.
I resolved this by wrapping my application with an additional component. That component is used in a Route so it also has access to the history prop.
<BrowserRouter>
<Route component={App} />
</BrowserRouter>
The App component subscribes on history changes, so I can do something whenever the route changes:
export class App extends React.Component {
componentWillMount() {
const { history } = this.props;
this.unsubscribeFromHistory = history.listen(this.handleLocationChange);
this.handleLocationChange(history.location);
}
componentWillUnmount() {
if (this.unsubscribeFromHistory) this.unsubscribeFromHistory();
}
handleLocationChange = (location) => {
// Do something with the location
}
render() {
// Render the rest of the application with its routes
}
}
Not sure if this is the right way to do it in V4, but I didn't find any other extensibility points on the router itself, so this seems to work instead. Hope that helps.
Edit: Perhaps you could also achieve the same goal by wrapping <Route /> in your own component and using something like componentWillUpdate to detect location changes.
React: v15.x, React Router: v4.x
components/core/App.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { BrowserRouter } from 'react-router-dom';
class LocationListener extends Component {
static contextTypes = {
router: PropTypes.object
};
componentDidMount() {
this.handleLocationChange(this.context.router.history.location);
this.unlisten =
this.context.router.history.listen(this.handleLocationChange);
}
componentWillUnmount() {
this.unlisten();
}
handleLocationChange(location) {
// your staff here
console.log(`- - - location: '${location.pathname}'`);
}
render() {
return this.props.children;
}
}
export class App extends Component {
...
render() {
return (
<BrowserRouter>
<LocationListener>
...
</LocationListener>
</BrowserRouter>
);
}
}
index.js:
import App from 'components/core/App';
render(<App />, document.querySelector('#root'));

React.js 0.14 - Required prop `clientId` was not specified in `WelcomePage`. Check the render method of `RoutingContext`

In my APP, which is the parent component of my react-router, I have this:
export default class APP extends Component {
static propTypes = {
children: PropTypes.element
};
constructor(props) {
super(props);
this.state = {
clientId: 'A134567897',
..........................
};
}
componentDidCount() {
CompanyInfo.getGenlInfo()
.then((response) => {
const data = response.data;
this.setState(data);
});
}
renderChild = () =>
React.cloneElement(this.props.children, {
// this needs to be passed to all child routes
...this.state
});
render() {
return (
<section>
<Header { ...this.state } />
{/* This is where the dynamic content goes */}
{ React.Children.map(this.props.children, this.renderChild) }
<Footer { ...this.state } />
</section>
);
}
}
In my router, a path of '/' brings up the WelcomePage (main page) component.
The WelcomePage component does appear as expected, and this.props.clientId does have a value, but if I code on the WelcomePage ....
WelcomePage.propTypes = {
clientId: PropTypes.string.isRequired
};
Required prop clientId was not specified in WelcomePage. Check the render method of RoutingContext.
I thought I took care of passing the clientId props to the WelcomePage through the APP's renderChild() method with '...this.state', didn't I?
Does anyone see where the error lies? Again, the clientID value does successfully pass to the WelcomePage, but I receive an error if I make it required on the WelcomePage.
Many Thanks
You can't use .isRequired on propTypes for route components.
This is because React checks propTypes at the time of element creation. React Router will initially create all of the route component elements with just the React Router props; by the time you get to the cloneElement call, this validation has already happened and failed.
We've recently added a note to the upgrade guide detailing this: https://github.com/rackt/react-router/blob/master/UPGRADE_GUIDE.md#routehandler
For more details see: https://github.com/facebook/react/issues/4494#issuecomment-125068868

How does React Router get into the React context?

I am looking at the redux-blog-example. There is SignupRoute.js which looks like this:
#connect(state => ({
auth: state.auth
}), {
signup
})
export default class SignupRoute extends React.Component {
static contextTypes = {
router: React.PropTypes.object
}
handleSubmit = (email, password) => {
const router = this.context.router;
this.props.signup(email, password, router);
}
render() {
return (
<Signup
auth={this.props}
handleSubmit={this.handleSubmit}
/>
);
}
}
How does the router get wired up to the context of this class?
It uses context, an undocumented but quite widely implemented React feature. For a full lowdown see this article, but here's the gist of it:
let router = Router(); // just illustrating, this is not how you instantiate React router
class MyComponent extends React.Component {
static contextTypes = {
router: React.PropTypes.object
};
render(){
// By declaring context type here, and childContextTypes
// on the parent along with a function with how to get it,
// React will traverse up and look for the `router` context.
// It will then inject it into `this.context`, making it
// available here.
}
}
class Parent extends React.Component {
static childContextTypes = {
router: React.PropTypes.object
};
getChildContext(){
return {
router: this.props.router
};
}
render(){
return <MyComponent />;
}
}
ReactDOM.render(<Parent router={router} />, document.getElementById('app'));

Resources