React-router, JWT Cookie, Flux, Authentification & SSR - reactjs

Hello StackOverflow community !
My application background is a bit complex but I feel like my problem isn't.
So I have a debut of application which consist of a Sign Up / Log In / Private component. My routes.js contains the following
// routes.js
function requireAuth(nextState, transition) {
if (!LoginStore.isLoggedIn()) {
transition.to('/login', null, { nextPathname: nextState.location.pathname });
}
}
<Route component={require('./components/App')} >
<Route path="/" component={require('./components/Home')} />
<Route path="/login" component={require('./components/LogIn')} />
<Route path="/signup" component={require('./components/SignUp')} />
<Route path="/private" component={require('./components/Private')} onEnter={requireAuth}/>
</Route>
Log In component retrieve from an API a JWT, store it in a LoginStore component (Flux design) and in a Cookie in order to have access to the Private component without re-login later on.
The whole is working correctly as when I'm logged, I have access to the Private component and I can access it when refreshing (thanks to the cookie).
My problem comes from the fact that I am also rendering this solution on the server and if I try to access directly to /private (by directly I mean my first call on the application is /private), I am redirected to /login and then I can access to /private. I would like to fall on /private at the first call.
// server.js
var location = new Location(req.path, req.query);
Router.run(routes, location, (error, initialState, transition) => {
console.log(initialState);
if (transition.isCancelled) {
return res.redirect(302, transition.redirectInfo.pathname);
} else {
var html = React.renderToString(<Router {...initialState}/>);
res.send(renderFullPage(html));
}
});
My LoginStore should retrieve the cookie and allow the access to the Private component but he doesn't succeed because my cookie cannot be find yet.
GET http://localhost/private [HTTP/1.1 302 Moved Temporarily 30ms]
GET http://localhost/login [HTTP/1.1 200 OK 51ms]
GET http://localhost/bundle.js [HTTP/1.1 200 OK 30ms]
I feel like I should send my cookie to the Router in the server.js so the LoginStore can be set but I don't know much about how to do it and it may not be the best solution. I would really appreciate help on this problem.
Thanks you in advance.

Your solution is similar to mine. Use react-cookie for manipulating cookies and patch save/delete method on the server on each request before calling Router.run. Also make sure your routes are sync.
import Iso from 'iso';
import React from 'react';
import ReactDomServer from 'react-dom/server';
import Router from 'react-router';
import Location from 'react-router/lib/Location';
import cookie from 'react-cookie';
import routes from '../../app/routes';
import Alt from '../../app/lib/Alt';
import AltBootstrap from '../lib/AltBootstrap';
export default {render};
function render(req, res, next) {
cookie.setRawCookie(req.headers.cookie); // THIS
cookie.save = res.cookie.bind(res); // THIS
let location = new Location(req.path, req.query);
Router.run(routes, location, (error, state, transition) => {
if (error) return next(error);
if (transition.isCancelled) return res.redirect(transition.redirectInfo.pathname);
AltBootstrap.run(state, req).then(snapshot => {
Alt.bootstrap(snapshot);
let markup = ReactDomServer.renderToString(<Router {...state}/>);
let html = Iso.render(markup, Alt.flush());
res.render('index', {html});
}).catch(next);
});
}
A complete source code is available here: isomorphic-react-flux-boilerplate.

Related

Building an API, beside a (React/Firebase) web app

I have a web app using React and Firebase Realtime Database.
The index.js file looks like the following:
import React from 'react';
import { userInfo } from 'os'
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
.....
class RouterBlock extends React.Component {
constructor(props) {
super(props);
this.state = {
logdInSatus: false
};
}
.....
render() {
.....
return (
<Router>
<Routes>
<Route exact path="/" element={<TopMenu/>} />
.....
<Route exact path="/p1" element={<Component_1/>} />
<Route exact path="/p2" element={<Component_2/>} />
<Route exact path="/p3" element={<Component_3/>} />
.....
</Routes>
</Router>
);
}
}
It all works pretty much the way I wish.
From this point, here is what I want:
An API for a customer, which is going to allow him to get data from my DB for his own use.
My app is now accessible through a URL like:
https://example.com/myWebApp
The API for the customer(s) should be used via this:
https://example.com/myAPI
and it would return JSON data from my DB that the customer can then use.
The top file for the API would probably look something like (but I am not yet sure):
const http = require("http")
import { getDatabase, ref, onValue} from "firebase/database";
import { url } from 'inspector'
import { workerData } from 'worker_threads'
const db = getDatabase();
.....
const requestListener = function (req, res) {
res.setHeader("Content-Type", "application/json")
res.writeHead(200)
res.end(`{"message": "This is a JSON response"}`)
// Here data needs to be taken from the actual DB.
}
const server = http.createServer(requestListener)
server.listen(port, host, () => {
console.log(`Server is running on http://${host}:${port}`)
})
Finally, my question is about the routing for the API url. Should it be one of the routes found in index.js or something totally separated? If this is the case (a route inside index.js), what is the proper syntax that I should use for this route? What I tried did not work.
Since all my current routes use a React component, I suppose this one needs to be different, but I don't know how to handle it. Any relevant hint will be welcome.

Let only one page be server-side rendered with Meteor

My Meteor/React application should render one static page besides the reactive one pagers with reactive UIs. The static package does not even need to be "hydrated" with the React magic after displayed in the browser. Though the server-side rendering on the server will be dynamic with React components.
I got it working, but I'm not sure if it is the intended official way to do it.
File import/client/routes.js
...
<Route path="/reactive/pages/:id" component={ReactiveComponent} />
<Route path="/static_url" />
...
File server/main.jsx
...
onPageLoad((sink) => {
if (sink.request.path === '/static_url) {
sink.renderIntoElementById('app', renderToString(
<StaticPage />,
));
}
});
...
File client/main.js
...
import { Routes } from '../imports/client/routes';
Meteor.startup(() => {
...
if (window.location.pathname !== '/offer_pdf') {
render(Routes, document.getElementById('app'));
}
});
...
Especially when rendering dependent on the URI, it seems a little bit hacky to me. Does a more elegant solution exist?
I don't think there is anything official, but in general, of course, it's a good idea to use a router for rendering different pages, so I thought it worth pointing out that you can use react-router on the server as well:
import React from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";
import { StaticRouter } from 'react-router-dom';
import App from '/imports/ui/app.jsx';
onPageLoad(sink => {
const context = {};
sink.renderIntoElementById("app", renderToString(
<StaticRouter location={sink.request.url} context={context}>
<App />
</StaticRouter>
));
/* Context is written by the routes in the app. The NotFound route, used
when, uhm, no route is found, sets the status code. Here we set it on the
HTTP response to get a hard 404, not just a soft 404. Important for Google bot.
*/
context.statusCode && sink.setStatusCode(context.statusCode);
// add title to head of document if set by route
sink.appendToHead(`<title>${context.title || 'My page'}</title>`);
});
In App you can then use the usual Switch and Route tags for specifying different routes. In that you could, for instance, only specify routes that you want to be server-rendered.

Gatsby wildcard route

Is it not possible to route to the same component with a wildcard path?
If in React I have something like:
<Router>
<Switch>
<Route path="/path/:id" children={<Component />} />
</Switch>
</Router>
all the requests:
/path/123
/path/123/p
/path/123/p/1
will route to the same /path/123
How can I tell Gatsby to do the same?
createPage({
path: `/path/123/*`,
component,
context
})
Or what is the solution to this problem, a redirect engine of some sorts?
I think you are looking for client-only routes. Given a page (or template if it's created from gatsby-node.js) you can:
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/Layout"
import SomeComponent from "../components/SomeComponent"
const App = () => {
return (
<Layout>
<Router basepath="/app">
<SomeComponent path="/path" />
</Router>
</Layout>
)
}
export default App
Note: assuming a src/pages/app/[...].js page (File System Route API structure).
When a page loads, Reach Router looks at the path prop of each component nested under <Router />, and chooses one to render that best matches window.location (you can learn more about how routing works from the #reach/router documentation).
Alternatively, you can use an automated approach (plugin: gatsby-plugin-create-client-paths) by:
{
resolve: `gatsby-plugin-create-client-paths`,
options: { prefixes: [`/path/*`] },
},
Which will validate all routes under /path.
Or for a more customizable approach, in your gatsby-node.js:
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/path/)) {
page.matchPath = "/path/*"
// Update the page.
createPage(page)
}
}
Disclaimer: These routes will exist on the client only and will not correspond to index.html files in an app’s built assets. If you’d like site users to be able to visit client routes directly, you’ll need to set up your server to handle those routes appropriately.

How to redirect to private route dynamically after social media login in react?

I have normal and social media login options on my page, and I have created a private Route as in https://reacttraining.com/react-router/web/example/auth-workflow.
The problem is that after redirecting to for example google and back, the state of the Router history is gone.
I have created a workaround in which I store the previous URL in local storage, when the user visits the login screen.
Do you guys have any better idea on how to save History state?
I think the main issue is that you're not persisting the data.
Start off by creating a service that checks for the current user. You can persist this data in localstorage, and upon logout it should clear out the credentials.
I did an implementation like this in my previous side project.
https://github.com/EliHood/fullstacktypescript
I have something like ...
export function* getAutoLoginStatus(action) {
try {
const login = yield call(api.user.currentUser);
const token = login.token;
if (login.user.googleId !== null) {
localStorage.setItem("googleId", login.user.googleId);
}
setAuthToken(token);
sessionData.setUserLoggedIn(token);
yield put(actionTypes.getUserSuccess(login));
} catch (error) {
localStorage.clear();
yield put(actionTypes.getUserFailure(error.response.data.message));
}
}
This saga is getting called on nav component, this checks to see if user is logged in or not.
componentDidMount() {
this.props.getUser();
}
You should consider using react-router-dom, you install it by going to your project folder on command line and put:
npm i react-router-dom
Go to your App.js file and import the following:
import { BrowserRouter as Router, Route} from 'react-router-dom';
Then at your App.js file, you should render something like:
render(){
return (
<div className="App">
<Router>
<Route path="/" exact component={() => <[YourHomeJSXComponent]/>} />
<Route path="/anotherPage" exact component={()=> <[AnotherJSXComponent]/>}/>
</Router>
</div>
);
}
Then, when you want to redirect to another page, you simply put the following command:
window.location.assign("/anotherPage");
And voila, every location.assign will register an entry on your navigation history.

Server side React Router user authentication

I'm running ReactRouter on top of a Koa server.
My Koa server is configured so that all requests point to 'index.html' (using koa-connect-history-api-fallback), which in turn forwards the requests to the ReactRouter. This all works great, except I'm having trouble figuring out how to do user authentication.
I want to protect my Routes so that users must be logged in to access any of the Routes. The problem is that my login page is one of the Routes, which means a user has to be logged in to access the login page!!
Is there a good way to get around this? For example, in my Koa server could I somehow protect all routes except for the '/login' route? I've read this example, which takes care of the authentication within the ReactRouter, but it seems sketchy to me to have your authentication on the client side. I could be off base though.
In case your curious, I'm working off react-redux-starter-kit
You could authenticate your top level components using a decorator, e.g.
// auth.js
import React, {Component} from 'react';
import { connect } from 'react-redux';
export default function(ComposedComponent) {
class Auth extends Component {
static contextTypes = {
router: React.PropTypes.object
}
componentWillMount() {
if (!this.props.authenticated) {
this.context.router.push('/');
}
}
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) {
this.context.router.push('/');
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return {authenticated: state.auth.authenticated};
}
return connect(mapStateToProps)(Auth);
}
and:
#auth
...your component...
Or you could do it in a less "reduxy" way if you had an auth module:
function requireAuth(nextState, replace) {
if (!auth.loggedIn()) {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
})
}
}
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="login" component={Login} />
<Route path="logout" component={Logout} />
<Route path="about" component={About} />
<Route path="dashboard" component={Dashboard} onEnter={requireAuth} />
</Route>
</Router>
), document.getElementById('example'))
In this instance I would leave the responsibility of protecting routes with ReactRouter, and use match on the server to figure out what route you actually want to render/redirect to when any request comes in.
Importantly, when a request comes to the Koa server, you run it through some authentication middleware that is able to tell you if the user is authenticated and their role. You then want this information reflected in the Redux store. You could either generate the store with an initial state something like this:
{
user: {
authenticated: true,
role: 'admin'
}
}
, or even better you could dispatch an action on the store where a reducer does this for you.
Now, when you create your routes on the server (passing in your store) React Router will know exactly what is okay and what is not. That is, if you have protected Routes with onEnter by checking user.authenticated, then calling match on the server will honour that and return a redirect to something like /login. An example onEnter might look like this:
const ensureLoggedIn = (nextState, replace) => {
if (!store.getState().user.authenticated)) {
replace('/login');
}
};
You can capture that redirect in redirectLocation which is an argument in the callback to match. Read all about match here. Then you can just use the servers res.redirect with the new location.
Of course, this type of route protection is just convenience, you will want to legitimately protect your API endpoints that contain sensitive information. But this method is invaluable because it uses the same logic for routing on the client and server with pretty much no effort.

Resources