Redwood v4.0.0 Upgrade Guide

Before we get started, just in case you missed them, here are quick links to the announcement post and Changelog:

Although this major is smaller in scope than the last one, auth is a big part of any project. Let’s go over the changes you’ll have to make.

Decoupled Auth

:information_source: This only affects you if you’re using Redwood’s auth.

Removed providers

We gave the good news first. Now, the bad news. We’ve removed the following providers:

  • Ethereum
  • GoTrue
  • Magic
  • Nhost
  • Okta

If you want to continue using one of these auth providers, you’ll have to set it up yourself via custom auth. To show you exactly how, we’ve updated the Custom Auth doc with step-by-step instructions, using Nhost as an example. (So if you were using Nhost, your job is that much simpler.)

Even though we removed the providers, it felt like a waste to just delete the code, especially since some of you may be using one of them. So instead, we moved the code to these new repos:

We hope that referencing one of these repos help ease your migration to v4. And If you’d like to maintain one of them, reach out and we’re happy to talk.

API changes

:information_source: We’re going to go step by step here; please follow along regardless of your auth provider and don’t hesitate to respond in this thread if you have any questions.

The rest of the changes are architectural. That is, we didn’t remove any functionality from the useAuth hook or anything like that, just moved things around. The most tedious change on your end will probably be changing where the useAuth hook gets imported from. It’s no longer from @redwoodjs/auth, but from src/auth. That’ll make more sense in a second, after we go through all the changes.

Let’s use auth0 as an example, but follow along even if you’re not using auth0—the steps here should apply to any auth provider.

First, make sure your git index is clean. Then, upgrade to v4.0.0:

yarn rw upgrade

To make this process less manual, we recommend re-running the original setup command you used to setup auth in the first place:

yarn rw setup auth auth0
# Say yes to overwriting the existing auth file

Ok, let’s look at the web and api changes one at a time. On the web side, you should notice a brand new file: web/src/auth.{ts,js}. This is where all the action happens. Previously, all the action happened in web/src/App.{tsx,js}. There, your auth provider’s client SDK was instantiated and passed to the AuthProvider component, like so:

import { Auth0Client } from '@auth0/auth0-spa-js'

import { AuthProvider } from '@redwoodjs/auth'

// ... 

const auth0 = new Auth0Client({ /* ... */ })

const App = () => (
  // ...
    <AuthProvider client={auth0} type="auth0">
  // ...
)

Now things are different. This all happens in web/src/auth.{ts,js}:

import { Auth0Client } from '@auth0/auth0-spa-js'

import { createAuth } from '@redwoodjs/auth-auth0-web'

const auth0 = new Auth0Client({ /* ... */ })

export const { AuthProvider, useAuth } = createAuth(auth0)

Notice that the AuthProvider component and the useAuth hook aren’t things you import from @redwoodjs/auth anymore, but things returned by a function called createAuth, imported from a new package (that the setup command installed for you), @redwoodjs/auth-auth0-web.
This function takes an instance of your auth provider’s client SDK and maps its methods to Redwood’s unified auth interface.

Since we’ve decoupled auth from the framework, the router doesn’t automatically pick this up anymore. You have to tell it about the integration by passing it the useAuth hook you just created:

+ import { useAuth } from 'src/auth'

  const Routes = () => {
    return (
+     <Router useAuth={useAuth}>
        {/* ... */}
      </Router>
    )
  }

Similarly, in web/src/App.{tsx,js}, the RedwoodApolloProvider needs the useAuth hook to get a token to include in every GraphQL request:

+ import { AuthProvider, useAuth } from 'src/auth'

  const App = () => (
    <FatalErrorBoundary page={FatalErrorPage}>
      <RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
+       <AuthProvider>
-       <AuthProvider client={auth0} type="auth0">
+         <RedwoodApolloProvider useAuth={useAuth}>
            <Routes />
          </RedwoodApolloProvider>
        </AuthProvider>
      </RedwoodProvider>
    </FatalErrorBoundary>
  )

There’s a good chance the logic for instantiating your auth provider’s client SDK is still here in web/src/App.{tsx,js} because you needed to pass it to the old version of the AuthProvider. We don’t need it in two places—go ahead and remove it.

That’s all for the web side. Well, almost—if you’re really using auth, you’re probably using useAuth in some of your components. You’ll have to change the import from @redwoodjs/auth to src/auth, but that should be easy enough with a simple search and replace.

By now, unless you’re using custom auth, there shouldn’t be any references to @redwoodjs/auth anymore. Go ahead and remove it from your web side’s package.json and update your project’s yarn.lock by yarn installing.

There’s not much to the api side. The only decoupling we had to do here was with the authDecoder, which, in auth0’s case, decodes the JWT in the Authorization request header. Now you pass it to the GraphQL server instead of it being implied. This gives you control of how the token is decoded:

+ import { authDecoder } from '@redwoodjs/auth-auth0-api'
  
  // ...

  export const handler = createGraphQLHandler({
    getCurrentUser,
+   authDecoder,
    // ...
  })

Hopefully that wasn’t too much. Here’s a summary of all the changes we made to your codebase for reference:

  • installed Redwood’s auth integration packages; in this case, @redwoodjs/auth-auth0-web and @redwoodjs/auth-auth0-api

  • made a new file, web/src/auth.{ts,js}; moved your auth provider’s client SDK instantiation from web/src/App.{tsx,js} to this new file

  • still in this new file, imported createAuth from @redwoodjs/auth-{yourAuthProvider}-web. called it with your instantiated client, createAuth(client), and exported its returns, AuthProvider and useAuth

  • passed the useAuth hook to the router in web/src/Routes.{tsx,js}

  • in web/src/App.{tsx,js}, wrapped your app with the AuthProvider from web/src/auth.{ts,js}, and passed the useAuth hook to RedwoodApolloProvider

  • anywhere the useAuth hook was used, changed its import to src/auth instead of @redwoodjs/auth

  • unless you’re using custom auth, removed the @redwoodjs/auth package from the list of dependencies in the web side’s package.json

  • on the api side, in api/src/functions/graphql.{ts,js} imported the authDecoder function from @redwoodjs/auth-{yourAuthProvider}-api and passed it to createGraphQLHandler

Using Clerk?

If you’re using Clerk, there’s one more recommended step for you: update to their new API keys. The Clerk team recently refactored their frontend API keys. They did this refactor in a backwards-compatible way, so huge shoutout to them, but definitely upgrade sooner than later. Check out their blog post about it for more details:

Node.js 16 and 18

:information_source: This change affects all users.

In your Redwood app’s package.json, expand the range in the node engines:

  "engines": {
-   "node": ">=14.19 <=16.x",
+   "node": ">=16.x <=18.x",
    "yarn": ">=1.15"
  },

Lastly, change the node version in your deploy provider to 16. This may involve updating a NODE_VERSION env var in your deploy provider’s UI, or changing the version in an .nvmrc file. Look to your deploy provider for more information.

Switch from cross-undici-fetch to @whatwg-node/fetch

:information_source: This is just a recommendation.

If you’ve followed along some of our how to’s, like Using a Third Party API, you may be using cross-undici-fetch on the api side. We recommend switching to @whatwg-node/fetch.

Extend from RedwoodError instead of RedwoodGraphQLError

:information_source: This is just a recommendation.

If you’ve read the Error Masking section in the GraphQL docs, you may be making your own custom errors by extending from RedwoodGraphQLError. While this’ll still work, we recommending extending from the new RedwoodError instead.

depthLimitOptions has changed to armorConfig

:information_source: This only affects you if you set the depthLimitOptions option in the createGraphQLHandler function, and can be applied via a codemod, or by manually updating the function in the api/src/functions/graphql.{ts,js} file.

Now that Redwood uses GraphQL Armor, the depthLimitOptions option in the createGraphQLHandler, where you set the max query depth, has changed to armorConfig. There’s a codemod to do this for you, or you can do it manually.

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary use-armor

Here’s how you’d do it manually:

 import { createGraphQLHandler } from '@redwoodjs/graphql-server'

 // ...

 export const handler = createGraphQLHandler({
   loggerConfig: { logger, options: {} },
   directives,
   sdls,
   services,
-  depthLimitOptions: { maxDepth: 6 },
+  armorConfig: { maxDepth: { n: 6 } },
   onException: () => {
     // Disconnect from your database with an unhandled exception.
     db.$disconnect()
   },
 })

matchPath is no longer exported from @redwoodjs/router

:information_source: This was an undocumented internal function that was exported; the odds this affects you are low.

The <NavLink> component takes a new prop, matchSubPaths, that makes it apply the class in the activeClassName prop to matching sub-paths:

<NavLink activeClassName="activeTest" matchSubPaths to="/users">
  Users
</NavLink>

For example, if you have a NavLink to /users and set matchSubPaths, if the user navigates to /users/2, the activeClassName still applies. The useMatch hook takes the same parameter.

The breaking part: the matchPath function, which is what this component uses under the hood, is no longer exported from @redwoodjs/router. If you’re using it, we recommend you use the useMatch hook instead. If useMatch doesn’t cover what you were using matchPath for, please let us know.

9 Likes

Well I will be checking this out in the next few days! Thanks!

Thank you and looking forward to v4! Are there any updates on React 18 support? Frankly, we were hoping it will be part of v4. With some other dependencies moving forward and dropping active support of <18 branches it becomes a little bit scary to fall behind too much and not to be able to catch up later since the app codebase grows rapidly.

1 Like

There is an active pull request here for React 18. feat(react): Upgrade to React 18 by virtuoushub · Pull Request #4992 · redwoodjs/redwood · GitHub I’m not sure where it stands but it has updates as of 3 days ago.

Hey just want to show thanks for the team making the hard decisions necessary to keep redwood clean, simple, and working out-of-the-box. Nothing is more frustrating for new developers than trying to use deprecated code and docs.

Now that these updates have been made I’d like to update the web3/ethereum package as needed. Maybe we can include a list of “community-maintained” options?

My only hesitation to not being a official package is losing support for copying files into the correct directories. This was a problem in the original package, and I’d really like to remove those steps completely.

On a related note, Redwood Auth is designed for only authentication, for only a single provider. But what if I want my users to login with google, and then also connect their Twitch username, or their Coinbase deposit address? There is currently no mention of OAuth anywhere in the documentation.

To solve this, I built a general-purpose OAuth2 client for redwood. It is a framework to fetch user data or authenticate using any OAuth provider (google, discord, coinbase, twitter, etc).

Is this something the community wants? A free, self-hosted oauth client in Redwood that gives developers complete control. Could this potentially be useful in the redwood stack?

6 Likes

Migrated a repo using azure auth and the migration worked like charm.
Thanks for providing the update, node 16, 18 and the documentation.

Is there a complete example of Custom Authentication | RedwoodJS Docs somewhere?

Just in case someone struggles with updating dbAuth and getting an error such as this.engine.dmmf is not a function: you have to delete node_modules and yarn.lock and run yarn install. It s an issue with new prisma version. [Bug?]: v4.0.0 Decouple Auth not working with dbAuth · Issue #7288 · redwoodjs/redwood · GitHub

3 Likes

Hey folks, just a heads up, I was testing v4.0.0 RC, everything was working fine until I enabled node 18 and the graphql function stopped working on netlify. it seems they are not supporting it yet.

Everything seems to work fine if I use v4.0.0 RC but keep node 16.

Maybe worth having a few more people test it out and mentioning it in the upgrade guide.

1 Like

@pi0neerpat: The answer is just YES! We are watching RW forums and issues with all OAuth related subject for months and your PoC seems the only viable steps for us so far.

We are mainly using Keycloak for our OIDC stack and therefore are really interested in a, maybe, future packaging of your solution done for Chess :wink:

Hey @razzeee, there isn’t but that’s a good idea—I’ll add an example repo and link to it in the custom auth docs.

@iam totally understand. We could’ve shipped React 18 in v4 but we were nervous about mixing it with Auth. Auth can be touchy, so we didn’t want other confounding variables while making “big” (mostly architectural, but those are big from the framework perspective) changes, and React 18 could’ve been a big confounding variable.

But we want to ship it too. We’re going to prioritize React 18 in v5, and the RC for v5 may come shortly (weeks or a month at most—not several months) after v4. v4 took a long time; we’ll make sure v5 is smaller and faster.

4 Likes

Thank you very much for the update!

Concering the new matchSubPaths how does it actually work?
Considering I have a route to view posts and it can have different optional view modes, I would like to have all sub paths of /post/{post} active.
<Route path="/posts/{post}/{view} name="post"/>
But within NavLink I need to supply the view parameter to routes, otherwise it will complain. Therefore, matchSubPaths has no effect. Though it would be really nice to have.
<NavLink activeClassName="activeTest" matchSubPaths to={routes.post({ post })} />

I was even considering rather using matchPath, but since it is not exposed anymore, I cannot use it. useMatchPath also has the inconvenience of being a hook, so it is inconvenient to check a path for multiple entries, i.e. a dynamic navbar.

Hey @dennemark, it sounds like the issue here is that NavLink with matchSubPaths still doens’t work for parametrized routes. It was implemented for non-paramterized routes, in that, in your example you can match all subpaths of /posts:

<NavLink
  activeClassName="activeTest"
  matchSubPaths
  to={routes.posts()}
>

So here activeClassName applies to /posts, /posts/1, /posts/1/modal (assuming “modal” is some kind of view), etc. Before, activeClassName would only apply to /posts. So in that sense, this is a new feature. But I understand what you’re going for.

We’re happy to take a feature request. And about exporting matchPath, we can reconsider it. But I think another problem here is that I’m pretty sure if you pass only one parameter to a route function, it doesn’t work. That is, routes.post({ post }) would probably throw in your example because view isn’t specified. Again, these are things can be implemented or fixed. We just happened to ship matchSubPaths the way it is because a contributor implemented it specifically for their needs.

Also I know you’ve been active in GitHub issues—I’ve seen but not had time to respond. We’ll try to pick up triage again soon. We’ve gotten back on PRs lately but need to come back around to issues faster.

I realised that useMatch already has the capabilities to test parametric routes! So I added some convenience variable to the routes object.

1 Like

@dom it seems to me that you are one of the busiest people I know. So, unless you already did that I could help with this task: using two different types (javascript / typescript) of the tutorial sample (or something else if this would not be your preference), using all relevant Authentication and Authorization service.

I’ve tried really hard to give all types of auth packages the same capabilities. We’re not giving dbAuth any special treatment inside the framework anymore. So now would be a great time to update your ethereum-auth implementation. IIRC you really wanted a specific file on the api side, that previously you couldn’t add, right? dbAuth has that, and now we’re working on the SuperTokens provider, which also needs that. So looking at any of those two would be a good start :slight_smile:

I’m happy to work with you to get everything working. Just ping me in a DM somewhere (here, discord or slack)