Should development mode mean "all bets are off?" - Netlify Identity

I’m a bit concerned about development mode and Netlify Identity. I didn’t think development mode meant “all bets are off”, especially when only intending to use development mode for the frontend.

I’m working on something that will be a little like running Jupyter Notebook on your own computer. Like Jupyter Notebook, I want users to be required to log in even on their local computer.

With Netlify Identity there is no jwt verification without Netlify Functions. In development and test mode, it decodes the jwt rather than verifying it. It can’t verify it because the secret needed to verify it isn’t available.

I think there should be a huge warning in development mode when there is a configuration where the local GraphQL endpoint can’t verify the token. Luckily there already is a huge warning that Redwood is pre-1.0.

Another use case besides a Jupter Notebook type thing, is with something like ngrok or localtunnel.

A sort of local mode could be made that would verify it, but it would need to call a Netlify Functions endpoint from the app to verify each token. Actually, I think that is doable with getCurrentUser in the graphql function. The local graphql’s getCurrentUser function could call the remote Netlify Functions graphql’s getCurrentUser using the token in the request. It could then cache it to memory or to disk (probably best to only cache the sub and roles and not the email for privacy reasons, which would mean the app would need to work without the email).

This gets to another issue which I might go straight to GitHub issues for once I gather a bit more info: in order to associate a Netlify login with a user account, I need the sub. I think it’s in the clientContext of the lambda function but not under the user key, and only the user key is passed onto getCurrentUser.

I figured out a good reason why Netlify Identity Widget may not be showing a warning on local development mode.

The simplest path with Netlify would be not running a local backend server at all, when running a local frontend server. If the local frontend is connected to a Netlify function, there is no reason for the backend code to call jwt.decode()

@bat

Netlify doesn’t make it easy to run its identity service in development mode.

If fact, its service relies on that service to extract the user and identity from the context:

exports.handler = async function(event, context) {
  const {identity, user} = context.clientContext;
  // Do stuff and return a response...
}

Visit our docs on Go functions to learn how to access the clientContext with Go.

The user object is present if the function request has an Authorization: Bearer <token> header with a valid JWT from the Identity instance. In this case the object will contain the decoded claims.

The identity object has url and token attributes. The URL is the endpoint for the underlying GoTrue API powering the Identity service. The token attribute is a short-lived admin token that can be used to make requests as an admin to the GoTrue API.

The token is in effect defacto verified by populating those identity and user values on the clientContext.

But, in dev mode running “locally” there’s no way to easily do that. I’m not even 100% certain Netlify exposes the JWT secret used to verify the token. As you noted: It can’t verify it because the secret needed to verify it isn’t available.

By way of example, both Supabase and Netlify is GoTure – in fact Supabase is a fork of Netlify’s GoTrue service.

If you compare th Redwood decoders, you’ll see that for Supabase, the token is verified:

https://github.com/redwoodjs/redwood/blob/main/packages/api/src/auth/decoders/supabase.ts#L11

For Netlify, if dev/test it is decoded

https://github.com/redwoodjs/redwood/blob/main/packages/api/src/auth/decoders/netlify.ts#L18

and in prod, the user is extracted from the clientContext (ie defacto verified):

If we knew the JWT Secret Netlify uses, we could easily verify it as well – but I’m not sure we can. Thought it would be worth asking Netlify.

Would that not be the user.id from clientContext? Although, I can see that the implementation for dev/prod would have to be different since one would have id and the other sub?

Would a console.warn() or warn log before the return jwt.decode(token) is done in the Netlify decoder suffice?

Maybe we can add some language in the Netlify auth docs to state emphatically that in dev mode tokens are not verified?

Happy to review a PR for these additions or language suggestions.

Now one thing that might work is to use Netlify’s GoTrue client and verify the token via a get user:

If you invoked your Netlify Function with the appropriate Authorization header (Authorization: Bearer <JWT here> ), our system will populate the context.clientContext object. You can then use this to information your function whether the request is valid or not.

As you suggest, though one would want to cache that in some way so that currentUser endpoint isn’t requested on each request.

One would have to add the GoTrue client to the api and configure the identity site url, but that could effectively verify the token via Netlify in dev.

Thanks for the detailed reply!

I am thinking that using development mode with real data should be avoided. Most of the time, when there isn’t a well-defined API and when there isn’t malicious code with access to the development server (locally or through localtunnel/ngrok or binding to 0.0.0.0), it won’t be an issue and a warning could suffice, however, supabase is more ideal.

For the warnings, I think both the docs and console.warn when the decode is ought to be added and I can make some PRs. Another place would be when the setup command is run.

I don’t think user.id is available but I will check again. I logged and all I remember having were the roles (empty in my case because I haven’t started using them yet).

In order to help avoid using real data in development, there should be easy to find documentation showing how to create a local production or staging api server and use it with a local development web server. Also netlify identity would be out of the picture without getting a secret jwt key or making a request to the Netlify Functions server in currentUser. I’d like to get that working.

Dev, not sure I 100% agree about not using “real” data, though I do wish there could be separate users in dev and prod (or staging) setup on Netlify identity. Real = production – then yes.

Interestingly, Netlify in their cli to try identity triggered events mocks a user:

if (name === 'identity') {
        // https://www.netlify.com/docs/functions/#identity-event-functions
        body.event = event
        body.user = {
          id: '1111a1a1-a11a-1111-aa11-aaa11111a11a',
          aud: '',
          role: '',
          email: 'foo@trust-this-company.com',
          app_metadata: {
            provider: 'email',
          },
          user_metadata: {
            full_name: 'Test Person',
          },
          created_at: new Date(Date.now()).toISOString(),
          update_at: new Date(Date.now()).toISOString(),
        }

and then in their option to mock a function invocation, they “emulate” test data with a hardcoded JWT:

 if (isAuthenticated) {
        headers = {
          authorization:
            'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb3VyY2UiOiJuZXRsaWZ5IGZ1bmN0aW9uczp0cmlnZ2VyIiwidGVzdERhdGEiOiJORVRMSUZZX0RFVl9MT0NBTExZX0VNVUxBVEVEX0pXVCJ9.Xb6vOFrfLUZmyUkXBbCvU4bM7q8tPilF0F03Wupap_c',
        }
        // you can decode this https://jwt.io/
        // {
        //   "source": "netlify functions:trigger",
        //   "testData": "NETLIFY_DEV_LOCALLY_EMULATED_JWT"
        // }
      }
    }

So, which one can mock RW users in test, even they are suggesting some dummy user identity data for local dev using their cli tool.

1 Like

I did some more testing and it seems the user ID (which comes from sub) is missing because the token expired. I am working on getting it refreshed.

I created an issue on GitHub for the expired token issue, so others don’t need to wait until I’m getting my project working to start fixing it, if it exists (I’m pretty sure it does).