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

fix(next-auth): provide request context in lazy initialization for auth() calls in RSCs #12653

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

huboh
Copy link

@huboh huboh commented Feb 14, 2025

  • add createRequestFromHeaders utility for consistent request creation
  • pass constructed Request to config function instead of undefined
  • enable request-based config customization and runtime modifications for auth() calls in RSCs, matching other contexts
  • support documented use cases like env-specific provider configuration
  • allow header/request access during server-side auth() calls

☕️ Reasoning

Lazy initialization is documented to provide request context access for customizing configuration, with examples like:

  • Accessing request data during server-side auth() calls
  • Adding different providers based on staging/dev environments
  • Customizing configuration and runtime modifications based on request headers

However, RSCs were receiving undefined instead of the request context, making these customizations impossible in RSC contexts. This fix makes auth() call in RSCs work consistently with other contexts (middleware, route handlers) and enables the documented use cases.

Example use case that was broken but now works:

const { auth } = NextAuth(
  async (req) => {
    if (req) {
      // This now works for `auth()` calls in RSCs too
      const headers = req?.headers
      const isStaging = headers?.get('host')?.includes('staging')
    }
  
    return {
      providers: [
        isStaging 
          ? StagingProvider() 
          : ProductionProvider()
      ]
    }
})

Previous Behavior

// RSCs received undefined
const _config = await config(undefined)

New Behavior

// RSCs now receive a proper Request object
const _request = createRequestFromHeaders(_headers)
const _config = await config(_request)

🧢 Checklist

  • Documentation
  • Tests
  • Ready to be merged

🎫 Affected issues

📌 Resources

- add createRequestFromHeaders utility for consistent request creation
- pass constructed Request to config function instead of undefined
- enable request-based config customization for RSCs, matching other contexts
- support documented use cases like env-specific provider configuration
- allow header/request access during server-side auth() calls
@huboh huboh requested a review from ThangHuuVu as a code owner February 14, 2025 00:13
Copy link

vercel bot commented Feb 14, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
auth-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 14, 2025 11:21am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
next-auth-docs ⬜️ Ignored (Inspect) Visit Preview Feb 14, 2025 11:21am

Copy link

vercel bot commented Feb 14, 2025

@huboh is attempting to deploy a commit to the authjs Team on Vercel.

A member of the Team first needs to authorize it.

@huboh
Copy link
Author

huboh commented Feb 14, 2025

Just wanted to share this while waiting for the merge, I had to implement a workaround in my company by forcing auth() calls in RSCs to go through the getServerSideProps/API route path instead of the RSC path. I do this by constructing and passing a fake context when auth() is called with no parameters:

const { handlers, signIn, signOut, auth } = NextAuth(
  async (req) => {
    if (req) {
      // code here will now have the request context for `auth()` calls inside RSCs
      // in addition with old behaviour inside route handlers, middleware and api routes or getServerSideProps
    } else {
      // no request context for `signIn()`, `signOut()` and `unstable_update()` calls...No need for that ? 
    }

    return {
      ...
    }
  }
);

const auth_ = async (...args: Parameters<typeof auth> | []) => {
  if (args.length) {
    return (
      auth(...args)
    );

  } else {
    // `auth()` was called inside rsc and lazy init function will now have request context
    const heads = new Headers(await headers());
    const reqUrl = (
      (heads.get("x-url")) ||
      (`${heads.get("x-forwarded-proto") || "http"}://${heads.get("host")}`)
    );
    const context: GetServerSidePropsContext = {
      resolvedUrl: reqUrl,
      query: {}, // not used by original `auth()`, so no need.
      req: (
        new NextRequest(reqUrl, { headers: heads }) as unknown as GetServerSidePropsContext["req"]
      ),
      res: (
        new NextResponse(null, { headers: heads }) as unknown as GetServerSidePropsContext["res"]
      )
    };

    // Force RSC calls to use getServerSideProps path
    return (
      auth(context)
    );
  }
};

export {
  handlers,
  signOut,
  signIn,
  auth_ as auth
};

@huboh huboh changed the title feat(next): provide request context in lazy initialization for auth() calls in RSCs feat(next-auth): provide request context in lazy initialization for auth() calls in RSCs Feb 14, 2025
@huboh huboh changed the title feat(next-auth): provide request context in lazy initialization for auth() calls in RSCs fix(next-auth): provide request context in lazy initialization for auth() calls in RSCs Feb 14, 2025
- Replace `Request` with `NextRequest` to match the expected type.
- Ensure the request is properly initialized with headers, cookies and other fields.
Copy link
Member

@ThangHuuVu ThangHuuVu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed PR and description. In my opinion, this is not the direction that we want though. I would prefer to alter the function's parameter to support passing headers, but not constructing the request object again without all the other context since the RSC world discourages having a global context from my understanding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants