I'm using supabase and trying to load the user session on the server side. If you refresh the page, it catches there is a user but not on first load (e.g. like when coming from a magic link). How can I ensure it does load before he page?
List item
Here is the page:
import router from "next/router";
import { supabase } from "../utils/supabaseClient";
function Home() {
const user = supabase.auth.user()
if (user){
//router.push('/admin') failsafe, not ideal
}
return (
<div className="min-h-screen bg-elkblue dark:bg-dark-pri">
marketing
</div>
);
}
export async function getServerSideProps({ req }) {
const { user } = await supabase.auth.api.getUserByCookie(req);
if (user) {
return {
redirect: {
destination: "/admin",
permanent: false,
},
};
}
return {
props: { }, // will be passed to the page component as props
};
}
export default Home;
You can use the auth-helpers for help with server-side rendering https://github.com/supabase-community/supabase-auth-helpers/blob/main/src/nextjs/README.md#server-side-rendering-ssr---withpageauth
Do note that it however needs to render the client first after OAuth because the server can't access the token from the URL fragment. The client will then read the token from the fragment and forward it to the server to set a cookie which can then be used for SSR.
You can see an example of that in action here: https://github.com/vercel/nextjs-subscription-payments/blob/main/pages/signin.tsx#L58-L62
Related
I just finished implementing Google social authentication in my NextJS + DjangoRest project following this blog post. I am trying to figure out how to make protected routes that will redirect users if they’re not logged in.
This is how I did it so far:
when user logs in, it saves the jwt_token in the cookie as httponly
uses axios with “withCredentials: true” to access the API endpoint which returns current user data(i.e. email)
saves the user data as a useContext(). When protected page loads, check if UserContext is empty or not and redirects to login page if it is empty.
The obvious problem is the UserContext is reset whenever user refreshes the page, even when the JWT token is still present in the cookies. And I have a feeling this isn’t the right way to implement this.
So how would I implement a similar feature in a non-hacky way? I cannot read jwt-token from cookies in the frontend as it is httponly. Is there a safe way to read user’s JWT token from cookies to test for authentication?
So if I am reading your question right then you can use getServerSide props on your page to detect if the user is authenticated with your api.
function Page({ isAuth }) {
return (
<>
<div>My secure page</div>
//if you return data from your token check api then you could do something like this
<div>Welcome back {isAuth.name}</div>
</>
)
}
export default Page
export async function getServerSideProps(context) {
const isAuth = await tokenChecker(context.cookies.jwt) // In your token checker function you can just return data or false.
if (!isAuth) { //if tokenChecker returns false then redirect the user to where you want them to go
return {
redirect: {
destination: `/login`,
}
};
}
//else return the page
return {
props: {
isAuth,
},
}
}
If this is not what you mean let me know and i can edit my answer.
I modified #Matt's answer slightly and typescript-friendly to solve my problem. It simply checks the user's cookies if they have a jwt_token value inside.
import cookies from 'cookies'
export const getServerSideProps = async ({
req,
}: {
req: { headers: { cookie: any } };
}) => {
function parseCookies(req: { headers: { cookie: any } }) {
var parsedCookie = cookie.parse(
req ? req.headers.cookie || '' : document.cookie
);
return parsedCookie.jwt_token;
}
const isAuth = parseCookies(req);
if (typeof isAuth === undefined) {
return {
redirect: {
destination: `/auth/sign_in`,
},
};
}
return {
props: {
isAuth,
},
};
};
I have a small dummy react application, I have created a "Home Page" after the user is logged on the home page context is set for user credentials from the home page and I am able to get it on the home page. but I have created another page "profile" after I change the route to profile in URL, there is no longer data. I understand that since the page is refreshed data is lost because data persistence is the job of databases. but if I have to query some common data for every page what advantage does context put on the table? thanks.
On Home Page below the code, I wrote to set user credentials.
Home.js
**const { userCredentails, setUserCredentails } = useUserContext();**
useEffect(() => {
const getInitialData = async () => {
const returnData = await AuthFunction();
if (returnData.success) {
**setUserCredentails(returnData.user);**
return dispatch({ type: true, userData: returnData.user });
}
return dispatch({ type: false });
};
getInitialData();
}, []);
Now if a want to access the same data on the profile I don't want to query the database how do get it from there.
**const cntx = useContext(userCredentialsContext);**
above code returns empty objects.
finally, this is the context.js page
import { useState, createContext, useContext } from "react";
export const userCredentialsContext = createContext({});
export const UserContextProvider = ({ children }) => {
const [userCredentails, setUserCredentails] = useState({});
return (
<userCredentialsContext.Provider
value={{ userCredentails, setUserCredentails }}
>
{children}
</userCredentialsContext.Provider>
);
};
export const useUserContext = () => {
const data = useContext(userCredentialsContext);
return data;
};
if you implement your routing by using an anchor tag() or window.location, then the whole page will refresh including your context, setting it back to default state, the right way to do it is by using the Link tag from react-router-dom, here is an example;
import { Link } from "react-router-dom";
<Link to="/home">Home</Link>
I am building a Next.js project where I want to implement private route similar to react-private route. In React this can be done by using react-router, but in Next.js this cannot be done. next/auth has the option to create private route I guess, but I am not using next/auth.
I have created a HOC for checking if the user is logged-in or not, but I'm not able to redirect the user to the private he/she wants to go after successfully logging in. How to achieve this functionality in Next.js? Can anybody help me in this?
This is the HOC, I used the code from a blog about private routing.
import React from 'react';
import Router from 'next/router';
const login = '/login'; // Define your login route address.
/**
* Check user authentication and authorization
* It depends on you and your auth service provider.
* #returns {{auth: null}}
*/
const checkUserAuthentication = () => {
const token = typeof window !== "undefined" && localStorage.getItem('test_token');
if(!token) {
return { auth: null };
} else return {auth:true};
// change null to { isAdmin: true } for test it.
};
export default WrappedComponent => {
const hocComponent = ({ ...props }) => <WrappedComponent {...props} />;
hocComponent.getInitialProps = async (context) => {
const userAuth = await checkUserAuthentication();
// Are you an authorized user or not?
if (!userAuth?.auth) {
// Handle server-side and client-side rendering.
if (context.res) {
context.res?.writeHead(302, {
Location: login,
});
context.res?.end();
} else {
Router.replace(login);
}
} else if (WrappedComponent.getInitialProps) {
const wrappedProps = await WrappedComponent.getInitialProps({...context, auth: userAuth});
return { ...wrappedProps, userAuth };
}
return { userAuth };
};
return hocComponent;
};
This is my private route code:
import withPrivateRoute from "../../components/withPrivateRoute";
// import WrappedComponent from "../../components/WrappedComponent";
const profilePage = () => {
return (
<div>
<h1>This is private route</h1>
</div>
)
}
export default withPrivateRoute(profilePage);
To redirect back to the protected route the user was trying to access, you can pass a query parameter with the current path (protected route path) when redirecting to the login page.
// Authentication HOC
const loginPath = `/login?from=${encodeURIComponent(context.asPath)}`;
if (context.res) {
context.res.writeHead(302, {
Location: loginPath
});
context.res.end();
} else {
Router.replace(loginPath);
}
In the login page, once the login is complete, you can access the query parameter from router.query.from and use that to redirect the user back.
// Login page
const login = () => {
// Your login logic
// If login is successful, redirect back to `router.query.from`
// or fallback to `/homepage` if login page was accessed directly
router.push(router.query.from && decodeURIComponent(router.query.from) ?? '/homepage');
}
Note that encodeURIComponent/decodeURIComponent is used because the asPath property can contain query string parameters. These need to be encoded when passed to the login URL, and then decoded back when the URL is used to redirect back.
Can you send me some examples of the page which only renders once.
I want to see how others implement this feature.
Since I use next.js I tried saving not in localstorage but using cookies and it works but in prod on vercel I can see this page WHEN I don't have to, it shows it for <1s and then show the correct page.
UPD:
It works but when I how the website using vercel this happends:
First time
Shows the welcome page
I press the button "NEXT" and I go to the next page
If I go back in doesn't allow me and it returns to the page on step 2
Once I close the google tab and reopen my website I:
See the welcome page for <0.5s
I am transferred to the page where I supposed to at first.
Maybe this is has to do with cookie i use
put the implementation of this page:
const firstTime = cookies(props).firstLaunch;
useEffect(() => {
const expire = new Date(new Date().getTime() + 7776000000000);
if (firstTime === undefined) {
document.cookie = `firstLaunch=false; expires=` + expire;
}
alert(localStorage.firstLaunch);
}, [firstTime]);
if (firstTime === "false") return <LoadingPage title={""} to={"/map"} />;
return (
... <=== welcome page
async function getStaticProps(ctx) {
return {
props: {
firstLaunch: cookies(ctx).firstLaunch,
},
};
}
I think you have to use getServerSideProps as getStaticProps runs on build time and cookies aren't available then. It's a bit different in development mode - static pages are rerendered on every change, so it may have looked to work properly.
Try this:
export async function getServerSideProps() {
const parsedCookies = cookie.parse(context.req.headers.cookie);
if (!parsedCookies.firstLaunch) {
return {
redirect: {
permanent: false,
destination: '/first-launch',
},
}
}
}
This way you can redirect user on server side without any content flash (0.5s). Your firstLaunch page must be available under some route though.
import Link from "next/link";
import React, {useEffect} from "react";
import { ArrowIcon } from "../components/ui/icons";
import cookies from "next-cookies";
import { useRef } from "react";
function Home() {
const cookieRef = useRef<Boolean>(true);
useEffect(() => { <=== I use useRef to make this block run only once
if(cookieRef.current) {
const expire = new Date(new Date().getTime() + 7776000000000);
document.cookie = `firstLaunch=yey; expires=` + expire;
cookieRef.current = false;
}
}, [cookieRef]);
return (
....
);
}
export default Home;
export async function getServerSideProps(ctx) {
const tmpCookies = cookies(ctx)
if (tmpCookies.firstLaunch) { <=== if it is not my first time
return {
redirect: {
permanent: false,
destination: '/map', <=== redirect
},
}
}
return { <==== if it is my first time don't do anything
props: {}
}
}
Thank you Aitwar for the idea of using getServerSideProps with redirect
I have a nextjs application with simple user authentication. That means there are some protected routes that I don't want logged out user to get access to. The app compiles successfully in development build and works as expected. But when I use next build, I get an error saying -
Error occurred prerendering page "/createNewAdditionalInfo". Read more: https://err.sh/next.js/prerender-error
Error: 'redirect' can not be returned from getStaticProps during prerendering (/createNewAdditionalInfo).
Here's the code -
export async function getStaticProps(ctx) {
let userObject;
let id;
const cookie = parseCookies(ctx);
if (cookie.auth) {
userObject = JSON.parse(cookie.auth);
id = userObject.id;
}
if (!id) {
return {
redirect: {
permanent: false,
destination: '/',
},
};
}
return {
props: {},
};
}
redirect from getStaticProps in nextJS
export async function getStaticProps({ params }) {
const db = await getDB()
//get id from url
const id = params.postId
//find post in db
const post = await db().collection("posts").findOne({ _id: id })
// The Redirect Happens HERE
//if no post was found - go to Home page
if (!post) {
return {
redirect: {
destination: "/",
},
}
}
return {
props: {
post: JSON.stringify(post),
},
}
}
export async function getStaticPaths() {
return {
paths: [],
fallback: true,
}
}
From the asker's comments:
I need some way to redirect page from server side.
Next.js provides this, see the documentation.
You can achieve this by just switching the function getStaticProps to getServerSideProps. The entire function will execute on the server and your redirection will happen.
e.g.
export async function getServerSideProps() {
return {
redirect: {
permanent: true,
destination: 'https://giraffesyo.io',
},
}
}
I was trying to run getStaticProps and getServerSideProps on firebase hosting. Firebase hosting only supports static site hosting. For anything backend related you have to use firebase functions.
I switched my hosting provider from firebase to vercel and everything works as it should be.