fetch data by useEffect in a server side rendering page - javascript

I have a project with react js and next js. I am developing a dynamic page, with getStaticPaths and getStaticProps. So I am fetching most of the data in getStaticProps to make the page be rendered on server side.
But there are some data which I can't fetch on server side, because it needs token which is stored in local storage.
The question is, if I use useEffect hook to fetch those data on client side, does this all process make any advantage for SEO?
Or I have to change structures, and store token in cookies to fetch all data on server side?
Update:
I want to check if user is logged in, and based on the result, show the page in different styles. But no user-related data is going to be rendered.
Right now, my code looks like this:
export default function BookDetail(props) {
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
React.useEffect(() => {
// It captures token from cookies
const token = getCookie("token");
// Then I need to confirm that token is valid from backend
if (token) {
setIsLoggedIn(true);
}
}, []);
return (
<div>
{ !isLoggedIn ? (
{props.res.data.title}
<br/>
{props.res.data.description}
) : (
{props.res.data.title}
<br/>
<button type="button" onclick={()=>{window.location.href='http://example.com';}}
)}
</div>
);
}

If you need a token to fetch said data, that data is probably related to the user? Hence, doesn't and shouldnt be considered with SEO.
If your data is not specifically for the user. Consider making it accessable without token.
Edit based on the comments here:
Fetching data inside useEffect will absolutely affect SEO. You want to display part of a book (text) for users that are not logged in. You check if users are logged in by a request from the useEffect, this is fine and standard.
If you want to Google to be able to read your book-text with crawlers you can not fetch it in useEffect, I suggest the following:
in your getStaticProps: Fetch the data (book text) and pass it to your page. Display this information by default.
Then in your useEffect you check if the user is logged in. If they are --> remove the text and render a button instead.
This way, Google will read it as you intend, while logged in users will only see a button.

You can check no problem on the server side whether a user is logged in only when you use getServerSideProps - getStaticProps are executed at a built time so there is no communication with whatever user inputs into the UI simply because thats a different time frame: building the app on the server, only when the app is built user can interact with it.
But getServerSideProps are not executed at a built time, yet there are still executed on the server side and since useEffect is a frontend API it won't work there. So there are two ways:
If you use NextAuth - you can use getServerSideProps and on the context object you have 'req' property and the property passed to the getSession function (you need to import that function) will tell you whether user has a session or not. Here is an example code snipet:
import { getSession } from "next-auth/react";
// some code here like your frontend component
export const getServerSideProps = async (context) => {
const { req, res } = context;
const session = await getSession({ req: req });
if (!session) {
return {
redirect: { destination: "/", permanent: false },
};
}
const email = session.user.email;
return {
props: { email: email, session },
};
};
Here is more on the subject from the official next docs:
https://nextjs.org/docs/authentication
If you don't use NextAuth I am sure you can attach your token to the context object like in the example above and read it in getServerSideProps except not use getSession as that is NextAuth API. haven't done it though.

Related

Next Authentication with node js api

I'm new to the Next js and developing the Next js website. I am stuck in multiple authentications with different routes and roles. How can I handle it in the next js?
Frontend: Next js
Backend: Node js with JWT (JSON web token).
Please guide me on what I should use to authenticate.
Thanks in advance.
I am assuming you have done a couple things with my answer below:
you are setting a http only authenticated cookie / signing it, expiring it etc
On api requests, you are validating this cookie
You can create a middleware.ts / .js file on the root of your project, something like the following (note I was using typescript, you can just remove the types if using javascript):
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const protectedPages = ["/something", "/foo", "/profile"];
export function middleware(request: NextRequest) {
if (protectedPages.find((page) => page === request.nextUrl.pathname)) {
const token = request.cookies.get("YOUR_TOKEN_NAME");
if (!token) {
// send the user back to the sign in / home page or wherever
const url = request.nextUrl.clone();
url.pathname = "/home";
return NextResponse.redirect(url);
}
}
}
You do not need to import this anywhere, just do this, get the cookie and you are done. No Cookie with the name you gave it, show them the door.
Take a read of the docs from if you want to know more, they explain things better than me :) https://nextjs.org/docs/advanced-features/middleware
Can also combine this with the above suggestion with getServerSideProps to pass the data as a prop to the component.

next js api route or call firestore directly from client side

Hi i'm new to next js and had a few questions regarding using firebase authentication client side and server side. I have both firebase admin and firebase client sdks setup in my project. I have a signup page user routes to. They fill in name email and password.
I'm using client side to authenticate with email and password createUserWithEmailAndPassword when the user submits the form on the signup page.
Questions:
[1]
I want to save the auth response of user name and uid to firestore db when user is created. Should I do this in the same submit handler as the auth response happens ?
const onSignup = async ({ email, password }: Tfields) => {
try {
const response = await auth.signup(email, password);
/ *** DO I save to firestore DB here ? **/
// route to authenticated page
router.push("/account");
} catch (error) {
});
}
};
[2]
If calling firestore from handler above should I be calling firestore directly using the firebase client side sdk or should I create an api route in nextjs called createDBUser and call the api from handler. Wouldn't this be safer connecting to firestore using api instead of directly with client side sdk ?
[3]
Example authorized route like /account this is essentially a SSG at build time.
Wont this show nothing on the page at first which is not very SEO friendly. Only the header SEO component will be viewable at first until the firebase client side check happens ?
const Account = () => {
const [user, setUser] = useState<any>(null);
const handleUser = (user) => {
setuser(user)
}
useEffect(() => {
const unsubscribe = onIdTokenChanged(getAuth(), handleUser);
return () => unsubscribe();
}, []);
return (
<div>
<Head>
<title>Authorized User page</title>
<meta
name="description"
content="this is john doe's page with all his account pages"
/>
</Head>
{user && <div>
<div>This is a authenticated account page</div>
</div> }
</div>
);
};
export default Account;
Or Should I be using getServerSideProps in the account page instead to check if user is logged in instead of doing this in the useEffect. Wouldn't this have all the content rendered on the server before its served to the user ? Also I would be able to inject users name inside the seo header as it will be rendered on server before hand.
Interacting with Firestore directly or via a server depends on your case and opinions may vary. But is it worth adding another API route, verifying user tokens and then adding data to Firestore when that can be directly and secured using security rules? Yes, you can add data to Firestore right after createUserWithEmailAndPassword() creates a user.
Routing Firestore requests via your API would be useful if you need any sort of rate limit on updating documents or any operations.
For server side rendered web apps, I would recommend using sessions cookies so you can authenticate user before render. Here you would have to verify the cookie using Admin SDK and and fetch data to show before page renders.

useEffect with function that update the state as dependency leads to infinite loop

Code Sandbox: https://codesandbox.io/s/new-breeze-d260k
The code:
This is a simple auth app
When you click Login, the accessToken is exchanged and stored in memory while the refreshToken is stored in localStorage.
While the accessToken is valid (here a timestamp), the Home page shows the protected content
At every page reload (i.e App initialization), the refreshToken is sent to the server and if it is valid, a new accessToken is exchanged.
The problem:
To refresh the token on App initialization, I have an onRefreshToken() function in a useEffect to be executed once (I wanted to pass an empty array as dependency but typescript/eslint complains and suggest that onRefreshToken() should be the dependency. I admit that I don't understand why this is recommended to have always a dependency when you want the effect to be executed once).
Once the token is renewed, I store the accessToken and the user profile in their respective context.
Infinite re-render loop begins. On my local server, this is due to setProfile() and not setAccessToken(). However I don't understand why.
Side note
The above issue is the main issue of this post but on a side note, the login/logout process don't sync between tabs so if you have any idea why, I would be happy to hear your advice on this point as well.
Happy new year
One way to fix this would be to check to see if you have an access token and only refresh it if you need to:
export default function App() {
const { accessToken } = useAuthContext();
const { onRefreshToken, onSyncLogin, onSyncLogout } = useAuth();
useEffect(() => {
const refresh = async () => {
await onRefreshToken();
};
!accessToken && refresh();
}, [onRefreshToken, accessToken]);

Correct way to implement Github OAuth in client-side application?

I'm adding OAuth into my github clientside application. I have the final auth token being returned successfully, but I feel like I hacked my workflow.
Current Architecture Flow:
1) User clicks href link from component to hit the initial OAUTH route
2) Retrieve token from Github for user identity
3) Github redirects to my server route and my server route sends an additional POST to the /access_token request page with the client_secret, id and code from the above step.
4) Finally I redirect from the above route back to my UI and set a URL parameter in the process
5) In the componentDidMount I strip the final auth token from the window.url and set it in my state
Note: I plan on storing the token in Redux later, but this is the base
level as to what I'm doing.
Actual Code
Server
app.get("/login", async (req, res) => {
// Get the identity token from GitHub origin
return await axios
.post("https://github.com/login/oauth/access_token", {
code: req.query.code,
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
})
.then(async resp => {
// Little hack to parse out the query params into an object
let data = await url.parse("http://parse.com?" + resp.data, {
parseQueryString: true
}).query;
res.redirect(
url.format({
pathname: Environment.getUrl(),
query: {
token: data.access_token
}
})
);
});
});
UI Authentication Component
export default class GithubAuthentication extends React.Component {
state = {
authToken: "DATA"
};
componentDidMount() {
let currUrl = window.location.href;
this.setState({ authToken: currUrl.split("token=")[1] });
}
render() {
return (
<React.Fragment>
<a href="https://github.com/login/oauth/authorize?client_id=b5cd37110eb31620aad7">
{this.state.authToken ? "Logout" : "Login With Github"}
</a>
<span>{this.state.authToken}</span>
</React.Fragment>
);
}
}
Questions
1) The one thing I wasn't able to figure out was to make the href link a controlled component and actually hit the auth URL with something like SuperAgent or Axios. Instead, I'm forced to use this href link, not sure why.
2) Is this actually a sensible flow for getting the final auth token?
Regarding question 2, from a security standpoint, it is better to keep access token on server-side and never send the token to client-side.
I couldn't find good written resources, so I'd like to share this video which sums up how to deal with access token clearly.
https://www.youtube.com/watch?v=CHzERullHe8&list=PL78z2Z3ZApYcKb-GDQt6ikdN2EDqmKJrT&index=12&t=419s
Take away from the video
We still don't have a good way to securely store the token on the browser
By storing the access token on the server-side and using session cookie, you can minimize the risk of access token being compromised.
To actually implement this flow, you can use cookie-session to generate session. You can also use github-passport to simplify the implementation.
https://github.com/expressjs/cookie-session
https://github.com/jaredhanson/passport-github
1) I think you should reorganize your app so that you can use a component instead of an href link. You would know whether you're authenticated or not based on the value on the state property. This value can be passed as prop to your component which is where you would put the logic of authenticated ? "Logout" : "Login" or anything else.
2) the flow is OK but you have to make sure you do server side validation of the token since it's easy to just flip the switch on the UI and the pretend you're authenticated very easily.

React / Redux and Swagger client

I'm trying to figure out the best way to structure a React / Redux app that will primarily use a swagger client for api access.
The problem is I'm not entirely sure where to store a reference to the swagger client. After logging in and obtaining a JWT auth token, I need to tell all subsequent requests to add the authorize header. With axios this is trivial because it persists it's headers until told otherwise. It doesn't appear the swagger client does this. So ideally, I would create a swagger client once upon login, add the header info and just reference it for all future requests (that way too it only fetches the schema json once in a single page application).
Since I'm doing this in the context of an action, would it be best to store the Swagger client in the Redux store (and how would I accomplish that)? Or would I create a static instance of it outside of Redux?
// app init
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
const store = createStoreWithMiddleware(reducers);
export const swaggerClient = { instance: authService.createFromState().then(() => {
ReactDOM.render(
<Provider store={store}></Provider>
...
);
});
do some login stuff, create swagger client:
// redux action
import { swaggerClient } from '../index';
// ... do login, get bearerToken
Swagger({
url: 'https://localhost/swagger/v1/swagger.json',
requestInterceptor(req) {
req.headers['Authorization'] = `Bearer ${bearerToken}`;
return req;
}
}).then((client) => {
// store reference for all future ajax calls
swaggerClient.instance = client;
});
and in case the page is refreshed, we need to rebuild the swagger client from the bearerToken in local storage
// authService
import { swaggerClient } from '../index';
function createFromState() {
// if authentication is known from localstorage, we can rebuild
// a swagger client
if(isAuthenticated()) {
const authentication = getAuthentication();
return Swagger({
url: 'https://localhost/swagger/v1/swagger.json',
requestInterceptor(req) {
req.headers['Authorization'] = `Bearer ${authentication.bearerToken}`;
return req;
}
}).then((client) => {
swaggerClient.instance = client;
return client;
});
}
}
I'm a little confused if this is the right direction, probably a newbie question. Having to wait for the swagger client to load while restoring from localstorage seems a kinda crazy way to do this (to prevent race conditions on future calls).

Categories

Resources