Giter Club home page Giter Club logo

next-firebase-ssr's Issues

Multiple token values in cookies

Hi, thank you for the awesome example, I love the way you designed so elegant the auth solution.
I was playing around and encountered that there are multiple cookies with the value token and I'm sure it comes from nookies. One major bug that could happen is that one of those token values can be undefined and then the client will be in such state where it is logged in via firebase but because of bad cookies the page won't be rendered if getServerSideProps check for token. Do you know what could be the reason for this or it is just my local dev environment?

Fix firebase import

In firebaseClient, you are importing the firebaseClient like this: import * as firebasClient from 'firebase/app'.

A breaking change has been pushed in the way firebase SDK v8.0.0 handles exports.

For anyone coming to your repo and tutorial, when they download firebase now, this breaks.

The syntax now is import firebaseClient from 'firebase/app'.

Here's the SO question for reference - https://stackoverflow.com/questions/64545862/upgrade-to-firebase-js-8-0-0-attempted-import-error-app-is-not-exported-from

Redirect on first load of authenticated page

I followed the example and redirected when the page isn't authenticated: https://github.com/colinhacks/next-firebase-ssr/blob/master/pages/authenticated.tsx#L25

As a result, when I load my website for the first time in a while, it redirects to /404. When I reload my authenticated page it works (bc the website regenerates my token).

Is there any way to resolve this? What's the preferred way to refresh your token?

This is my full error message:

ERROR:  FirebaseAuthError: Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
    at FirebaseAuthError.FirebaseError [as constructor] (error.js:44:28)
    at FirebaseAuthError.PrefixedFirebaseError [as constructor] (error.js:90:28)
    at new FirebaseAuthError (error.js:149:16)
    at FirebaseTokenVerifier.mapJwtErrorToAuthError (token-verifier.js:223:20)
    at token-verifier.js:207:25
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async getServerSideProps (authenticated_page.js:29:19)
    at async Object.renderToHTML (render.js:473:24)
    at async doRender (next-server.js:1127:38) {
  errorInfo: {
    code: 'auth/id-token-expired',
    message: 'Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
  },
  codePrefix: 'auth'
}

Cannot read properties of undefined (reading 'apps')

Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'apps')

Call Stack
eval
webpack-internal:///./firebaseClient.js (25:68)
./firebaseClient.js
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/pages/management/transactions.js (2412:1)
options.factory
/_next/static/chunks/webpack.js (621:31)
webpack_require
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/webpack.js (37:33)
fn
/_next/static/chunks/webpack.js (276:21)
eval
webpack-internal:///./src/layouts/SidebarLayout/Header/Userbox/index.js (19:72)
./src/layouts/SidebarLayout/Header/Userbox/index.js
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/pages/management/transactions.js (2599:1)
options.factory
/_next/static/chunks/webpack.js (621:31)
webpack_require
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/webpack.js (37:33)
fn
/_next/static/chunks/webpack.js (276:21)
eval
webpack-internal:///./src/layouts/SidebarLayout/Header/index.js (11:66)
./src/layouts/SidebarLayout/Header/index.js
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/pages/management/transactions.js (2610:1)
options.factory
/_next/static/chunks/webpack.js (621:31)
webpack_require
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/webpack.js (37:33)
fn
/_next/static/chunks/webpack.js (276:21)
eval
webpack-internal:///./src/layouts/SidebarLayout/index.js (8:65)
./src/layouts/SidebarLayout/index.js
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/pages/management/transactions.js (2643:1)
options.factory
/_next/static/chunks/webpack.js (621:31)
webpack_require
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/webpack.js (37:33)
fn
/_next/static/chunks/webpack.js (276:21)
eval
webpack-internal:///./pages/management/transactions/index.js (6:83)
./pages/management/transactions/index.js
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/pages/management/transactions.js (2445:1)
options.factory
/_next/static/chunks/webpack.js (621:31)
webpack_require
file:///D:/WORK/PROJECTS/PAID/dashboard-forex/.next/static/chunks/webpack.js (37:33)
fn
/_next/static/chunks/webpack.js (276:21)
eval
node_modules\next\dist\build\webpack\loaders\next-client-pages-loader.js?absolutePagePath=D%3A%5CWORK%5CPROJECTS%5CPAID%5Cdashboard-forex%5Cpages%5Cmanagement%5Ctransactions%5Cindex.js&page=%2Fmanagement%2Ftransactions! (5:15)
eval
node_modules\next\dist\client\route-loader.js (235:50)

Quetsion: Leaking firebase credentials

Won't using this method leak your credentials to the front end? Wouldn't it be better to use API routes to isolate the use of firebase admin to the server-side only?

An error, ERR_HTTP_HEADERS_SENT, has occurred.

Hi, I'm shimar.
Thank you for a very nice example.

I tried to run this in my environment.
Then, ERR_HTTP_HEADERS_SENT has occurred when the link "Go to authenticated route" in top page clicked.

Do you know what caused it?

↓、This is the output to the terminal when an error occurs

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:543:11)
    at DevServer.sendHTML (/Users/shimar/developments/repos/github.com/vriad/next-firebase-ssr/node_modules/next/dist/server/next-dev-server.js:31:5)
    at DevServer.render (/Users/shimar/developments/repos/github.com/vriad/next-firebase-ssr/node_modules/next/dist/next-server/server/next-server.js:56:37)
    at async Object.fn (/Users/shimar/developments/repos/github.com/vriad/next-firebase-ssr/node_modules/next/dist/next-server/server/next-server.js:32:107)
    at async Router.execute (/Users/shimar/developments/repos/github.com/vriad/next-firebase-ssr/node_modules/next/dist/next-server/server/router.js:38:67)
    at async DevServer.run (/Users/shimar/developments/repos/github.com/vriad/next-firebase-ssr/node_modules/next/dist/next-server/server/next-server.js:49:494)
    at async DevServer.handleRequest (/Users/shimar/developments/repos/github.com/vriad/next-firebase-ssr/node_modules/next/dist/next-server/server/next-server.js:18:101) {
  code: 'ERR_HTTP_HEADERS_SENT'
}

Best regard.

Remember me feature direction

Hi @colinhacks

Thank you for the repo and your article on Next.js Firebase auth. They are really helpful!

I'm wondering if we can have something like "Remember Me" when sign-in using the techniques you explained. I see that the persistence set as session in firebaseClient.ts.

firebaseClient
    .auth()
    .setPersistence(firebaseClient.auth.Auth.Persistence.SESSION);

I read the auth-persistence article in Firebase link and I was thinking of making a switch/checkbox in login page that set the session. A base example logic would be like this:

if (rememberMeCheckboxIsChecked){
  firebaseClient
      .auth()
      .setPersistence(firebaseClient.auth.Auth.Persistence.LOCAL);
} else {
  firebaseClient
      .auth()
      .setPersistence(firebaseClient.auth.Auth.Persistence.SESSION);
}

Do you think something like this would be possible or do you there will be pitfalls doing the "Remember me" this way?

How could Firebase claims be used with this implementation?

I am using Firebase auth and Firestore for my app, and basically used the same flow as described/used in this repo, but I have 2 types of users and will have 2 types of UI depending on the user role. Default role will be a student, but in the event they upgrade to a Stripe subscription, they assume the role of coach and can take on students of their own. I have found that claims are the best way to implement this, but aside from that knowledge, actually using them properly is a concept that still escapes me.

Server-side cookie generation

Hello, thank you for the informative walkthrough. I want to implement an authentication flow using Firebase but I'm between exchanging the token for a cookie server-side or setting the cookie myself client-side on onIdTokenChanged (as shown in this repo). Could you enlighten me why I should choose one way or another?! Is there some benefit in doing this client-side vs generating the cookie through the admin SDK?

Firebase security rule

Thanks a lot for sharing this approach!
When fetching data from firebase, it works fine without security rule applies, but got stuck when login required, like this: request.auth != null.

Token duplication

Hi, I've got again duplicated tokens, even with the recent fixes. Cannot attach steps to reproduce, but what I was doing is login in and out from time to time and then somehow I've got those duplicates. Attached screenshot for more clarity.

https://prnt.sc/vufsay

How do you secure an API route?

Thanks a lot for sharing this approach! I'm curious if you have any suggestions as to how I can secure an API route? I tried to just verify that the token exists and is valid, but calling this in my API returns the error that the token expired

From my secure page

export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
  try {
    const cookies = nookies.get(ctx);
    console.log(JSON.stringify(cookies, null, 2));
    const token = await firebaseAdmin.auth().verifyIdToken(cookies.token);
    const { uid, email } = token;

    // the user is authenticated!
    // FETCH STUFF HERE
    const data = await getDataFromAPI();

    return {
       props: data
    }

my API. This throws the error

export default async (
  { query: { id }, cookies }: NextApiRequest,
  res: NextApiResponse<Data>
) => {
  try {
    await firebaseAdmin.auth().verifyIdToken(cookies.token);
    const data = getDataHere();

    if (data) {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'application/json');
      res.end(JSON.stringify(data));
    }
  } catch (error) {
    res.status(401).json({ message: 'Not Authorized!' });
  }
};

Guide seems to not work with NextJS 10

This guide seems to no longer work with nextjs 10+.

Tested using code with my own specifications and copy paste from this repo.
I also tested using both nookies and js-cookies

What happens:
Token gets set correctly on login page, I can see the token in the applications panel of my chrome devtools. Whenever the token is used in SSR the token is shown as invalid by firebase and deleted from the front end cookie storage.

When trying to use this token on a SSR page the token dissapears, firebase gives an error stating

    code: 'auth/argument-error',
    message: 'First argument to verifyIdToken() must be a Firebase ID token string.'

My auth.tsx (at this point copy paste from this repo, tried replacing nookies with js-cookies but got the same issue)

//auth.tsx
const AuthContext = createContext<{ user: firebaseClient.User | null }>({
    user: null,
});

export function AuthProvider({ children }: any) {
    const [user, setUser] = useState<firebaseClient.User | null>(null);

    useEffect(() => {
        if (typeof window !== "undefined") {
            (window as any).nookies = nookies;
        }
        return firebaseClient.auth().onIdTokenChanged(async (user) => {
            console.log(`token changed!`);
            if (!user) {
                console.log(`no token found...`);
                setUser(null);
                nookies.destroy(null, "token");
                nookies.set(null, "token", "", { path: '/' });
                return;
            }

            console.log(`updating token...`);
            const token = await user.getIdToken();
            setUser(user);
            nookies.destroy(null, "token");
            nookies.set(null, "token", token, { path: '/' });
        });
    }, []);

    useEffect(() => {
        const handle = setInterval(async () => {
            console.log(`refreshing token...`);
            const user = firebaseClient.auth().currentUser;
            if (user) await user.getIdToken(true);
        }, 10 * 60 * 1000);
        return () => clearInterval(handle);
    }, []);

    return (
        <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
    );
}

export const useAuth = () => {
    return useContext(AuthContext);
}
//authenticatedPage.tsx
export const getServerSideProps = async (req, ctx) => {
  try {
    const cookies = nookies.get(ctx);
    //Always fails here
    const token = await firebaseAdmin.auth().verifyIdToken(cookies.token);
    console.log("Token is:", token)

    return {
      props: {
        data: "Worked!"
      },
    };
  } catch (err) {
    const cookies = nookies.get(ctx);
    return {
      props: {
        data: "Did not worked!"
      },
    };
  }
};

const Revisions = ({ data, ctx }) => {
  const { user } = useAuth();
  return (
    <Layout>
      <Container>
        {/* always returns no user signed in */}
        <p>{`User ID: ${user ? user.uid : 'no user signed in'}`}</p>
        <p>test</p>
      </Container>
    </Layout>
  );
};

export default Revisions

Relevant versions:

    "firebase": "^8.2.10",
    "firebase-admin": "^9.5.0",
    "next": "10.2.3",
    "nookies": "^2.5.2",
    "react": "17.0.1",

What have I tried?
Shifting from nookies to js-cookies
Using the encode option with nookies

nookies.set(null, "token", token, { path: '/', encode: v => v  });

Edit:
Tested with NextJS 11, same issue.
It seems like the issue happens in auth.tsx when token is being unset if no user is present. The token gets unset when ran un an SSR page.

    return firebaseClient.auth().onIdTokenChanged(async (user) => {
      if (!user) {
        setUser(null);
        // Cookies.set("token", "Satt fra greia");
        // nookies.set(null, "token", "",);
        return;
      }

by commenting out the destruction of the cookie things begin working. But this seems to be like a very sub-optimal and possible insecure solution.

Compatibility when hosting via Firebase Functions

Thanks so much for this very helpful example repo. I wanted to leave a note here (since it was where I initially searched for the solution) that the cookie name token is not compatible as of writing with Firebase's experimental support for turnkey Next.js deployments. This is because Firebase Functions only accept one cookie by the name of __session. (SO source) While the example provided in this repo worked fine in development, the call to firebaseAdmin.auth().verifyIdToken(cookies.token) would fail in production due to Firebase stripping the token cookie. I switched the saved cookie's name to __session in both the AuthProvider and in authenticated.tsx and that resolved the issue for me, not sure if there are any potential pitfalls of doing this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.