Feedback Request: Authentication patterns in Next.js #63775
Replies: 11 comments 27 replies
-
I'm curious about patterns for authed-only data fetching functions when tokens need to be refreshed (and haven't expired), and the client should get updated cookies. |
Beta Was this translation helpful? Give feedback.
-
This is extremely helpful! Thanks for putting it together. A couple questions - Could you elaborate a little more on why layouts aren't sufficient for protecting parts of your app? Right now we are checking for that the user is authorized inside of our /dashboard/[accountId] route, but then all of the more deeply nested routes e.g. /dashboard/[accountId]/settings do not check for user privileges, because we just assume that if the user is on that page, they must be authorized to see it. This has been very convenient because most developers don't have to think about auth while adding new pages, but it sounds like this opens up a vulnerability, so I'd like to understand more why that's not recommended. It sounds like the vulnerability exists because the layout doesn't re-render when the user navigates, so technically a user could have their permissions revoked and still see the app as long as they don't close their browser? Is that it? My other question is around server actions. We assume that all server actions should be protected as if they were regular old POST endpoints accessible by anyone. Is that accurate and could you elaborate a little more on the recommended approach for protecting server actions? |
Beta Was this translation helpful? Give feedback.
-
👋 Colin from Clerk here The two mistakes we see most for authorization are:
We'd love to see a first-class authorization pattern that mitigated these mistakes, in addition to education. |
Beta Was this translation helpful? Give feedback.
-
I think a LOT of people use Cognito because it's thought of as the cheapest and most reputable auth provider in the game. I also think a lot of people would end up using Amplify for a simpler SDK to work against Cognito with. The v6 announcement post celebrates support of Next.js middleware which is great news for probably many companies who have single region servers (and maybe the decision to be multi-region is not in their control), but have an opportunity to be edge-first for authentication. The example in their blog post even does auth-gated routing! But @sebmarkbage suggests that nobody should be doing that and Middleware isn't the place for auth checks. I just assume I'm doing it wrong, so I'm excited for this discussion and the documentation it will yield... but I'm hoping that the ideal option(s) are as simple as adding an auth gate to middleware 😂 For now, I'm stuck doing client-side only authentication if I'm not meant to be authenticating via middleware. I have content that's static (not dynamic), but auth-protected... So, PPR incoming means I guess I'd just add auth to each and every one of my RSCs? If that's the case, I'd want to know if there's a simple way for me to hide all the content of the static page until the first authenticated request of any RSC on the route is successful? It doesn't seem like that'd be viable. |
Beta Was this translation helpful? Give feedback.
-
Instead of explicit Data Transfer Objects, is it acceptable to modify |
Beta Was this translation helpful? Give feedback.
-
I’d be nervous about using any cache for any requests having to do with security. I’d say as a rule don’t cache any auth headers. |
Beta Was this translation helpful? Give feedback.
-
PS: I am very pleased and grateful for this thread. I have recently completed an integration with next-auth, but it's better to clarify things. |
Beta Was this translation helpful? Give feedback.
-
These would be useful: Providing configurable option for supporting nodejs middleware. You can easily achieve this by editing a few files but of course relying on source edits is not good for production code. But still there's no reason middleware can't be arbitrary code IF it's running on nodejs. An initialization script ( would be basically same than middleware if single server) that can provide data for any subsequent functions using asynclocalstorage. Basically like how headers and cookies are accessed but for user data object ( or basically any data). This would make it easier to do one auth check at the start and then access the auth status/user data anywhere else directly if needed. Also it's much more secure to have it in one place than relying on checks littered over the codebase. Also component code could be more synchronous when user/auth data can be accessed directly and not thru some async getSession method. Also for auth libraries it would be easier to have such single standard point to hook into. "do your check, save results here in this format". That would also provide an abstraction for the auth implementation so changing auth library wouldn't require any changes elsewhere. |
Beta Was this translation helpful? Give feedback.
-
Hey there! Thanks for this extensive writeup @delbaoliveira 💯 FWIW I have written a comprehensive tutorial about using Lucia Auth in Next 14 and its App Router. I followed the best practices provided by Lucia, but I wasn't super sure about authorization in Layouts vs Middleware. Related discussion that I started: lucia-auth/lucia#1469 Perhaps this piece from @pilcrowonpaper should be taken into the discussion too: https://pilcrowonpaper.com/blog/middleware-auth/ |
Beta Was this translation helpful? Give feedback.
-
Hi @delbaoliveira, thanks for this write up. A couple clarifying questions: Is it okay to then pass the user from this layout.tsx, down to a client provider? import { getUser } from '@/app/lib/dal' export default async function Layout({ children }) { return ( Why does moving the getUser() to a different file make any difference?
But then your example shows
This may just be a lack of understanding on my part, but I don't understand why moving getUser() out to a separate file makes any difference here, when you say the issue is that layouts do not re-render on navigation. Is it just that wrapping getUser in cache() that makes the difference here? Even then, I don't understand how that solves the issue with layout.tsx not re-rendering on navigation. Thank you! |
Beta Was this translation helpful? Give feedback.
-
Is the use of I see it being used in the examples/sanity-cms, as shown below: next.js/examples/cms-sanity/sanity/lib/token.ts Lines 11 to 15 in 9a1cd35 In this example, if I remove the above code, will Next.js expose my API token? As far as I can tell (see my GitHub search), this is the only experimental feature used in this example, so it would be my only reason for switching to an experimental version of React. The React API reference for experimental_taintUniqueValue clearly warns "Experimental versions of React may contain bugs. Don’t use them in production." |
Beta Was this translation helpful? Give feedback.
-
Hey there. I’m Delba, a Developer Advocate at Vercel 👋
I’ve been working on clarifying how to implement auth in the App Router, including what React and Next.js APIs to use.
While there isn’t one single way to implement auth, there are best practices and patterns to ensure performance and security. This discussion aims to increase our collective understanding of why certain APIs and patterns are better than others.
We are currently documenting these here, and I’ve summarized them below.
I’d appreciate feedback on the proposed patterns, including anything we have not yet considered, and any questions you may have.
Terminology
To ensure we use consistent terminology, and to help beginners understand the principles of auth, here’s a quick description of concepts mentioned:
Summary
Here’s a summary of best practices and APIs:
Authentication
Server Actions
to implement sign-up and login functionality.useFormState()
can be used to display validation errors.useFormStatus()
can be used to handle pending states.Session Management
cookies()
API.“server-only”
package to ensure utils are kept on the server.Authorization
All parts of your application should be considered when implementing authorization. Unlike SPAs which have a single entry point, allowing you to wrap your app in an auth context provider, Next.js apps have multiple entry points, e.g. Layouts, Pages, Route Handlers, Server Actions.
Middleware
jose
, but notjsonwebtoken
.redirect()
currently cannot be invoked in Middleware.NextResponse.redirect
can be used instead.Data Access Layer
cache
API to memoize the returned values of data requests or your verify/update session utils during a render pass.Example:
Data Transfer Objects
Example:
Server Components and Layouts
Example:
Layouts
❌ You may be tempted to check the user’s auth state, then fetch data:
✅ Instead, check the auth state in a Data Access Layer. This guarantees that wherever
getUser()
is called within your application, the auth check is performed.Client Components and Context Providers
context
is not supported in Server Components, making them only applicable to Client Components.taintUniqueValue
API to prevent sensitive session data from being exposed to the client.This works, however, any
children
Server Components will be rendered on the server first, and will not have access to the context provider’s session data:Currently exploring ways to read session data on the server, and pass it to the client. Suggestions welcome.
Server Actions and Route Handlers
Treat Server Actions and Route Handles with the same security considerations as public API endpoints. Always verify user is allowed to perform the action before continuing.
Example:
Finally, this rough diagram shows an overview of the possible auth paths in Next.js, highlighting React and Next.js APIs used.
Beta Was this translation helpful? Give feedback.
All reactions