I'm looking for how to get the currently signed in Firebase user with Next.js through getServerSideProps. I've already tried using verifyIdAuthToken but I can't seem to change the persistence type through Firebase to get the cookie because I get this error:
[t [Error]: The current environment does not support the specified persistence type.] {
code: 'auth/unsupported-persistence-type',
a: null
}
My current code is this:
// utils/firebaseClient.js
import firebase from 'firebase';
const firebaseConfig = {
// redacted
}
if(!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION);
} else {
firebase.app();
}
const auth = firebase.auth();
const googleProvider = new firebase.auth.GoogleAuthProvider();
export { firebase, googleProvider };
// pages/dashboard.js
import { firebaseAdmin } from '../utils/firebaseAdmin';
import nookies from 'nookies';
export default function Dashboard({ user }) {
if (!user) {
return (
<>
<h1>Loading</h1>
</>
)
}
return (
<>
<h1>Hi</h1>
</>
);
}
export async function getServerSideProps(ctx) {
console.log(nookies.get(ctx));
try {
const cookies = nookies.get(ctx);
console.log(cookies);
const user = await firebaseAdmin.auth().verifyIdToken(cookies.token);
} catch (err) {
console.error(err);
return {
redirect: {
destination: '/',
permanent: false
}
}
}
return {}
}
// pages/index.js
import firebase from 'firebase';
import { googleProvider } from '../utils/firebaseClient';
export default function Authentication() {
firebase.auth().onAuthStateChanged((user) => {
if(user) window.location.replace('/dashboard');
});
const googleButton = () => {
firebase.auth().signInWithPopup(googleProvider).then((result) => {
window.location.href = '/dashboard';
}).catch((err) => {
return console.log(err);
});
}
return (
<>
<button onClick={googleButton}>Login with Google</button>
</>
)
}
Does anyone know how I can fix this, or if there is an alternate way to get the current Firebase user in getServerSideProps?
By the way, I have already read how to get firebase use in nextJS getserversideprops and the solution didn't work for me. It also seems like it didn't work for the author of that question. Thanks in advance!
Related
import React, { useState, useEffect } from "react";
import Axios from "axios";import { Link, Navigate } from "react-router-dom";
import "../assets/css/login.css";import "react-toastify/dist/ReactToastify.css";
import API from "../backend";
const Signout = () => {const [values, setValues] = useState({reDirect: false,});
const { reDirect } = values;
if (typeof window !== "undefined") {localStorage.removeItem("TWjwt");
const axiosGetCall = async () => {
try {
const res = await Axios.get(`${API}/logout`);
// enter you logic when the fetch is successful
console.log(`data here: `, res);
setValues({ ...values, reDirect: true });
} catch (error) {
// enter your logic for when there is an error (ex. error toast)
console.log(`error log: `, error);
}
};
axiosGetCall();
}
return <>{reDirect === true ? <Navigate to="/" /> : <></>}</>;};
export default Signout;`
hello,
i'm trying to learn react and above code has no problem instead the code is hitting my backend api multiple times for single logout..
i have removed <React.StrictMode> but still it is hitting apis multiple times..
i have understood that when my state changes the code is running again and again..
so any solutions ?
useEffect(() => {
const axiosGetCall = async () => {
const res = await Axios.get(`${API}/logout`);
// enter you logic when the fetch is successful
console.log(`data here: `, res);
if (typeof window !== "undefined") {
localStorage.removeItem("TWjwt");
}
setValues({ ...values, reDirect: true });
};
axiosGetCall();
}, []);
OK- Issue was Solved...
I am trying to set an array of objects into dineIns after fetching them inside useEffect. What I understood is that there is some kind of delay in receiving the data because the state variable returns an empty array when I log it after fetching the data.
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
import jwtDecode from 'jwt-decode';
function CheckIns() {
const [dineIns, setDineIns] = useState([]);
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
const user = jwtDecode(token);
if (!user) {
localStorage.removeItem('token');
navigate('/login');
} else {
async function UserData(user_email) {
const user_data = await axios
.get(`/api/users/${user_email}`)
.then((res) => {
const info = res.data.reservations;
setDineIns(info);
console.log(dineIns);
});
}
UserData(user.email);
}
} else {
navigate('/login');
}
}, []);
}
What needs to be corrected here to set the state in time?
set state is an async operation, which log the data after set it, will log the old value.
To ensure that the data set correctly, you can use setState again
const info = res.data.reservations
setDineIns(info)
setDineIns(prev => {
console.log(prev)
return prev;
})
Or you can use effect with dineIns dependence.
I think your code works fine.
You are expecting a Promise from the axios call but you are awaiting it.
Try to change your code like this:
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
const user = jwtDecode(token);
if (!user) {
localStorage.removeItem('token');
navigate('/login');
} else {
async function UserData(user_email) {
try {
const { data } = await axios.get(`/api/users/${user_email}`);
setDineIns(data.reservations);
console.log(dineIns);
} catch (err) {
console.log(err);
}
}
UserData(user.email);
}
} else {
navigate('/login');
}
}, []);
Laravel in PHP made this easy with https://laravel.com/docs/9.x/session#flash-data, so I figured Next.js would have an easy way too.
I thought I'd be able to do something like:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
ctx.res.setHeader("yourFlashVariable", "yourFlashValue");
console.log('headers', ctx.res.getHeaders()); // Why is it not even appearing here?
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
const props = ...
return { props };
};
and then in my other page:
export const getServerSideProps: GetServerSideProps = async (context) => {
const { headers, rawHeaders } = context.req;
// look inside the headers for the variable
// ...
But the header doesn't appear.
If you know how to achieve the goal of a flash variable (even if not using headers), I'm interested in whatever approach.
(Originally I asked How can I show a toast notification when redirecting due to lack of session using Next-Auth in Next.js? but now feel like I should have asked this more generic question.)
UPDATE
I appreciate the reasonable suggestion from https://stackoverflow.com/a/72210574/470749 so have tried it.
Unfortunately, index.tsx still does not get any value from getFlash.
// getFlash.ts
import { Session } from 'next-session/lib/types';
export default function getFlash(session: Session) {
// If there's a flash message, transfer it to a context, then clear it.
const { flash = null } = session;
console.log({ flash });
// eslint-disable-next-line no-param-reassign
delete session.flash;
return flash;
}
// getNextSession.ts
import nextSession from 'next-session';
export default nextSession();
// foo.tsx
import { getSession } from 'next-auth/react';
import { GetServerSideProps, InferGetServerSidePropsType, NextApiRequest, NextApiResponse } from 'next';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
const req = ctx.req as NextApiRequest;
const res = ctx.res as NextApiResponse;
const nSession = await getNextSession(req, res);
nSession.flash = 'You must be logged in to access this page.'; // THIS LINE CAUSES A WARNING
console.log({ nSession });
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
// ...
return { props };
};
// index.tsx
import { GetServerSideProps } from 'next';
import getFlash from '../helpers/getFlash';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getNextSession(context.req, context.res);
let toast = getFlash(session);
console.log({ toast });
if (!toast) {
toast = 'no toast';
}
console.log({ toast });
return {
props: { toast }, // will be passed to the page component as props
};
};
Also, the nSession.flash = line causes this warning:
warn - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
Your first code is working fine for me (printing the headers in terminal). However, the combination will not work as intended because the headers you set in /foo (say) will be sent to browser, along with a status code of 307, and a location header of /. Now "the browser" will be redirecting to the location and it won't forward your headers. Similar threads: https://stackoverflow.com/a/30683594, https://stackoverflow.com/a/12883411.
To overcome this, you can do something like this. This works because the browser does send the cookies (in this case, set when you create a session).
// lib/session.ts
import type { IronSessionOptions } from 'iron-session'
import type { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
export const sessionOptions: IronSessionOptions = {
password: process.env.SECRET_COOKIE_PASSWORD as string,
cookieName: 'sid',
cookieOptions: { secure: process.env.NODE_ENV === 'production' },
}
declare module 'iron-session' {
interface IronSessionData {
flash?: string | undefined
}
}
export const withSessionRoute = (handler: NextApiHandler) =>
withIronSessionApiRoute(handler, sessionOptions)
export const withSessionSsr = <P extends Record<string, unknown> = Record<string, unknown>>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) => withIronSessionSsr(handler, sessionOptions)
// pages/protected.tsx
import type { NextPage } from 'next'
import { getSession } from 'next-auth/react'
import { withSessionSsr } from 'lib/session'
const ProtectedPage: NextPage = () => <h1>Protected Page</h1>
const getServerSideProps = withSessionSsr(async ({ req, res }) => {
const session = await getSession({ req })
if (!session) {
req.session.flash = 'You must be logged in to access this page.'
await req.session.save()
return { redirect: { destination: '/', permanent: false } }
}
return { props: {} }
})
export default ProtectedPage
export { getServerSideProps }
// pages/index.tsx
import type { InferGetServerSidePropsType, NextPage } from 'next'
import { withSessionSsr } from 'lib/session'
const IndexPage: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ flash }) => {
// TODO: use `flash`
}
const getServerSideProps = withSessionSsr(async ({ req }) => {
// if there's a flash message, transfer
// it to a context, then clear it
// (extract this to a separate function for ease)
const { flash = null } = req.session
delete req.session.flash
await req.session.save()
return { props: { flash } }
})
export default IndexPage
export { getServerSideProps }
This also works if you want to set flash data in an API route instead of pages:
import { withSessionRoute } from 'lib/session'
const handler = withSessionRoute(async (req, res) => {
req.session.flash = 'Test'
await req.session.save()
res.redirect(307, '/')
})
export default handler
Complete example: https://github.com/brc-dd/next-flash/tree/with-iron-session
So I'm creating authentication logic in my Next.js app. I created /api/auth/login page where I handle request and if user's data is good, I'm creating a httpOnly cookie with JWT token and returning some data to frontend. That part works fine but I need some way to protect some pages so only the logged users can access them and I have problem with creating a HOC for that.
The best way I saw is to use getInitialProps but on Next.js site it says that I shouldn't use it anymore, so I thought about using getServerSideProps but that doesn't work either or I'm probably doing something wrong.
This is my HOC code:
(cookie are stored under userToken name)
import React from 'react';
const jwt = require('jsonwebtoken');
const RequireAuthentication = (WrappedComponent) => {
return WrappedComponent;
};
export async function getServerSideProps({req,res}) {
const token = req.cookies.userToken || null;
// no token so i take user to login page
if (!token) {
res.statusCode = 302;
res.setHeader('Location', '/admin/login')
return {props: {}}
} else {
// we have token so i return nothing without changing location
return;
}
}
export default RequireAuthentication;
If you have any other ideas how to handle auth in Next.js with cookies I would be grateful for help because I'm new to the server side rendering react/auth.
You should separate and extract your authentication logic from getServerSideProps into a re-usable higher-order function.
For instance, you could have the following function that would accept another function (your getServerSideProps), and would redirect to your login page if the userToken isn't set.
export function requireAuthentication(gssp) {
return async (context) => {
const { req, res } = context;
const token = req.cookies.userToken;
if (!token) {
// Redirect to login page
return {
redirect: {
destination: '/admin/login',
statusCode: 302
}
};
}
return await gssp(context); // Continue on to call `getServerSideProps` logic
}
}
You would then use it in your page by wrapping the getServerSideProps function.
// pages/index.js (or some other page)
export const getServerSideProps = requireAuthentication(context => {
// Your normal `getServerSideProps` code here
})
Based on Julio's answer, I made it work for iron-session:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '#/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }
So I have a very simple Next.js application that I would like to add authentication to using next-iron-session and the withIronSession function. Inside of my api folder, I have a simple component that grabs the login form data, performs a complex authentication action, then sets the user using
req.session.set("user", { email });
import { withIronSession } from "next-iron-session";
const VALID_EMAIL = "user";
const VALID_PASSWORD = "password";
export default withIronSession(
async (req, res) => {
if (req.method === "POST") {
const { email, password } = req.body;
if (email === VALID_EMAIL && password === VALID_PASSWORD) {
req.session.set("user", { email });
await req.session.save();
console.log(req.session.get());
return res.status(201).send("");
}
return res.status(403).send("");
}
return res.status(404).send("");
},
{
cookieName: "MYSITECOOKIE",
password: '2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8'
}
);
After the user is set in the session, I return a 201, and the client redirects to my protected page, where I try to grab that user in the getServerSideProps function using the withIronSession as a wrapper.
import { withIronSession } from "next-iron-session";
export const getServerSideProps = withIronSession(
async ({ req, res }) => {
const user = req.session.get("user");
console.log(user);
if (!user) {
res.statusCode = 403;
res.end();
return { props: {} };
}
return {
props: { }
};
},
{
cookieName: 'MYSITECOOKIE',
password: "2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8"
}
);
However, even though the user is found in the API function after it is set, ({"user", { email }}), that same session object returns {} in the getServerSideProps function in my protected component, which in my case always results in a 403. Is there a way to access the user that is set in the login component in the getServerSideProps function?
Note*
I was trying to follow this article, maybe it provides more information.
https://dev.to/chrsgrrtt/easy-user-authentication-with-next-js-18oe
next-iron-session works with node version > 12. It worked for me.
import React from "react";
import { withIronSession } from "next-iron-session";
const PrivatePage = ({ user }) => (
<div>
<h1>Hello {user.email}</h1>
<p>Secret things live here...</p>
</div>
);
export const getServerSideProps = withIronSession(
async ({ req, res }) => {
const user = req.session.get("user");
if (!user) {
res.statusCode = 404;
res.end();
return { props: {} };
}
return {
props: { user }
};
},
{
cookieName: "MYSITECOOKIE",
cookieOptions: {
secure: process.env.NODE_ENV === "production" ? true : false
},
password: process.env.APPLICATION_SECRET
}
);
export default PrivatePage;
I believe that this happens only on an http origin. Are you testing on localhost or any other non secure origin? If yes, then you need to specify secure: false in the cookieOptions.
From the documentation of next-iron-session, it is set to true by default, and this means that only secure origins (typically, origins with https) will be allowed.
Consider updating cookieOptions with secure: false for development environment and secure: true for production.
Like this:
withIronSession(
async ({ req, res }) => {...},
{
cookieName: 'MYSITECOOKIE',
cookieOptions: {
secure: process.env.NODE_ENV === 'production' ? true : false
},
password: '2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8'
}
);
I faced the same problem & found a simple solution:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '#/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }