Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Netlify Identity support in SSR #107

Closed
iRyanBell opened this issue Mar 5, 2021 · 7 comments
Closed

Netlify Identity support in SSR #107

iRyanBell opened this issue Mar 5, 2021 · 7 comments
Assignees
Labels
priority: low type: bug code to address defects in shipped code

Comments

@iRyanBell
Copy link

iRyanBell commented Mar 5, 2021

Describe the bug

Inside a Next.js project, after logging in with the netlify-identity-widget, the request's clientContext should provide service-side access to the user's authentication state.

To Reproduce
Steps to reproduce the behavior:

  1. Create a new Next.js project
  2. Add plugin-nextjs
  3. Sign In using netlify-identity-widget
  4. Retrieve the Identity details on the request's clientContext with Next.js SSR using:
export const getServerSideProps = async ({ req }) => {
  const {
    netlifyFunctionParams: {
      context: { clientContext }
    }
  } = req;
    
  const { user } = clientContext;
  ...
}

Expected behavior
The user object in the clientContext is undefined after signing in with the Netlify Identity Widget and visiting the page.

On the client side, I can confirm that the user is authenticated from the netlifyIdentity.on('init', callback) handler.

I'm not sure if I'm missing a simple build configuration setting or environment variable to access this value from the Identity service as described here: https://github.com/netlify/next-on-netlify#using-netlify-identity

Versions

  • Next.js: 10.0.7 (also tested with 9.5.3)
  • plugin (if installed at fixed version): 2.0.1

If you're using the CLI to build

  • OS: macOS 11.2.1
  • netlify/cli version: 3.10.1 darwin-x64 node-v12.19.1

If you're using file-based installation

  • netlify.toml:

Netlify Plan

  • Starter
@lindsaylevine lindsaylevine added type: bug code to address defects in shipped code priority: low labels Mar 5, 2021
@dan-cooke
Copy link

dan-cooke commented Mar 5, 2021

Having the same issue, glad to see you posted this.

To add the matter.

  • netlifyFunctionParams is undefined entirely in development mode.
  • netlifyFunctionParams.context.clientContext is defined when hosted on netlify itself, however only identity is populated. The user info is not populated

@dan-cooke
Copy link

dan-cooke commented Mar 8, 2021

@iRyanBell

I came up with this workaround HOF in the meantime. Honestly I've had a terrible time using the netlify JS libraries. After wasting about a week trying to get identity working with the various libraries, I've resorted to a semi-bespoke solution.

When you log a user in via identity, you need to set a cookie nf-jwt which is equal to the users JWT token, which can be retrieved from the returned response when logging in.

When you log them out, clear this.

Then use the following HOF to provide server side authentication to all your pages

const withNetlifySession = (innerHandler) => {
  return async ({ req, res }) => {
    const token = req?.cookies?.[`nf-jwt`];
    let user;
    try {
      const userRes = await fetch(`${url}/.netlify/identity/user`, {
        headers: {
          authorization: `Bearer ${token}`,
        },
      });
      user = await userRes.json();
    } catch (e) {
      console.error(e);
    }

    return innerHandler({ req, res, user });
  };
};

This allows you to access user object in your getServerSideProps with

export const getServerSideProps = withNetlifySession(
  async ({ user }: { user: User }) => {
    if (!user) {
      return {
        redirect: {
          destination: `/login`,
          permanent: true,
        },
      };
    }
    return {
      props: {
        user,
      },
    };
  },
);

This solution is hacky because netlify seems to set a nf_jwt token when you login, but this is not sent to Next.JS for whatever reason - if anyone knows how to send all site cookies to Next I would be very grateful

@iRyanBell
Copy link
Author

iRyanBell commented Mar 8, 2021

@dan-cooke It is tempting to just grab some off-the-shelf firebase auth, and keep on coding.

I've thought about using the ServerSideProps to call a lambda to get the data, but I don't like the idea of needing these two calls.

What I'm going to roll with for now is skipping the SSR auth, and just running the frontend updates in an Effect hook with state from netlify-identity-widget, then performing the authenticated actions from a Netlify lambda. The tradeoff on the UX is a skeleton loading indicator while that takes place.

@dan-cooke
Copy link

dan-cooke commented Mar 8, 2021

@iRyanBell I considered the same thing honestly. But I found that the useEffects for redirecting the user in various scenarios can become quite complex and difficult to mantain.

And in some cases, you don't want the user to even land on the page at all if they are not authenticated. This will result in a flash of skeleton text, then reroutes. Server side authentication just seems like a cleaner solution and will provide better UX in the end.

I think my solution above is pretty much what this plugin is doing under the hood anyway. So until they fix this issue, I will continue using this.

@iRyanBell
Copy link
Author

@iRyanBell I considered the same thing honestly. But I found that the useEffects for redirecting the user in various scenarios can become quite complex and difficult to mantain.

And in some cases, you don't want the user to even land on the page at all if they are not authenticated. This will result in a flash of skeleton text, then reroutes. Server side authentication just seems like a cleaner solution and will provide better UX in the end.

I think my solution above is pretty much what this plugin is doing under the hood anyway. So until they fix this issue, I will continue using this.

On the unauthenticated redirect front, I was looking at this as a very cool way to set that up declaratively:
https://www.netlify.com/blog/2019/01/31/restrict-access-to-your-sites-with-role-based-redirects/

But, it's a $99/mo feature ($$$!)

@tbgse
Copy link

tbgse commented Oct 25, 2021

I stumbled upon this after reading through netlify/next-on-netlify#20 it seems like this used to be supported in the older version of the plugin but is not anymore? At least netlifyFunctionParams seems to be undefined for me for API routes

@ericapisani ericapisani self-assigned this May 26, 2022
@ericapisani
Copy link
Contributor

I'm not sure if I'm missing a simple build configuration setting or environment variable to access this value from the Identity service as described here: https://github.com/netlify/next-on-netlify#using-netlify-identity

As pointed out by other folks in this issue, this functionality is missing between this newer iteration of the NextJS plugin and the previous one (next-on-netlify), so this isn't an error on your part.

You can read more in the section below on the context of why this functionality doesn't exist within this iteration of the plugin, but other than what was suggested as a workaround by @dan-cooke (thank you for providing that!) it's also possible that you could get the user object from the nf_jwt cookie that is attached to subsequent requests after signing in with the netlify-identity-widget.

The object that's encoded as the nf_jwt is the same one that you would have received as part of the clientContext.user in the req as seen in the old NextJS plugin documentation.

As a result, you could decode the token within getServerSideProps to get the user object (using a parser function such as this which I was able to do successfully in my local environment).

The only difference between this approach and what you would receive from directly querying the Identity service is verification that the JWT is valid because Netlify doesn't give the JWT secret to folks using the Identity service.

Sorry that this answer took so long in getting to you. Hopefully this workaround helps, and if you (or others) have additional questions, feel free to reach out to me here.

Additional background/context

@tbgse is correct in that the older version of the plugin used to support this.

To give some background on the previous state of things and why we don't currently have the same functionality in this iteration of the plugin:

The Next.js server expects to be run through an Express app, rather than via a lambda event, so we (Netlify) needed some kind of compatibility layer to bridge between them.

In the old plugin, we used a custom compatibility layer which is how we could attach the extra props that you see in the related documentation for Netlify Identity in the old plugin README.

In this iteration of the plugin, we’re instead using the same compatibility layer that Vercel uses.

As a result, we lost the ability to modify the request object ourselves, but no longer need to worry about keeping it compatible which we felt was a worthwhile tradeoff.

Since compatibility is the main goal of the plugin, whenever possible we’ve aimed to use Vercel’s implementation of things, which is why we are where we are today with the difference in Netlify Identity functionality between the previous NextJS plugin (next-on-netlify) and this one.

@ericapisani ericapisani closed this as not planned Won't fix, can't repro, duplicate, stale Jun 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority: low type: bug code to address defects in shipped code
Projects
None yet
Development

No branches or pull requests

5 participants