next-auth
next-auth copied to clipboard
fix(next-auth): Add support for Next.js basePath configuration
☕️ Reasoning
When I add basePath configuration to Next.JS config, the API routes are broken. If both AUTH_URL and NextAuth basePath configurations are empty, NextAuth will look like it works correctly. But, OAuth redirects callback URI is omitted basePath, so will fail.
In case of settings with AUTH_URL with full path: http://localhost:3000/basepath/api/auth, or adding NextAuth basePath configuration like /basepath/api/auth, NextAuth will raise Unknown Action Error.
I’ve investigated this error and figured out that it is caused by the Next.JS specification or bug. NextAuth parses the NextRequest URL and creates an action with the request URL. But, Next.JS with basePath configuration reports that the URL does not contain basePath. The simple Next.JS project here reproduces this issue. https://github.com/k3k8/nextjs-base-path-issue
This issue has been reported in the Next.JS repository, but has not yet received any responses from the members. (https://github.com/vercel/next.js/issues/60956) I suspect that the Next.JS app router should rewrite from an inbound URL to an internal one. Thus this is likely to be a specification, in my opinion.
Consequently, I’ve decided to fix this problem with next-auth components. The problem is caused by the Next.JS specification; my commits are only on the next-auth package. No commits are on core libraries.
I’ve created reqWithBasePathURL and estimateBasePath on packages/next-auth/src/lib/env.ts and modified reqWithEnvURL call point.
Configuration sample: Next.Js baesPaht: “/base_path”, NextAuth config.basePath: “/base_path/api/auth” The reqWithEnvURL called POST or GET request, and req: NextRequest contains like this: http://localhost:3000/api/auth/session The estimateBasePath function estimates from the request URL and config.BasePath.
If you set a Next.JS basePath
such as /base_path
, the NextAuth config should be contain full path to auth dir.
AUTH_URL: http://localhost:3000/base_path/api/auth, or basePath: /base_path/api/auth.
Configuring the full path to AUTH_URL is a v4 specification, and this correct configuration also works.
AUTH_URL: http://locahost:3000/, basePath: /base_path/api/auth
These configuration rules are obeyed on document one (https://authjs.dev/reference/warnings#env-url-basepath-redundant).
🧢 Checklist
- [x] Documentation
- [x] Tests
- [x] Ready to be merged
🎫 Affected issues
#9274 #9984 #10009
📌 Resources
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 | Aug 1, 2024 10:24am |
1 Skipped Deployment
Name | Status | Preview | Comments | Updated (UTC) |
---|---|---|---|---|
next-auth-docs | ⬜️ Ignored (Inspect) | Visit Preview | Aug 1, 2024 10:24am |
@k3k8 is attempting to deploy a commit to the authjs Team on Vercel.
A member of the Team first needs to authorize it.
Eagerly looking forward to see this PR into production, the basePath configuration is quite necessary.
@ThangHuuVu, @balazsorban44 Is there any way you can check / merge this PR? auth-js is currently unusable for me because I need to have my application behind a basePath.
+1 on this, my app needs a basePath as well
even my app needs a basePath as well
When using SessionProvider, I was able to specify basePath in layout.tsx like so and get it to work. Had to provide hostname without which it won't work. Not sure of the implications, any thoughts on this?:
.env.local
BASE_PATH=/basepath
layout.tsx
<SessionProvider basePath={`http://localhost:3000${process.env.BASE_PATH}/api/auth`}>
I have tried configuring a base path with NextAuth including putting the basePath in authConfig, in SessionProvider with the relative or the full URL, as the AUTH_URL. Sadly, none of them work, waiting for this fix to get merged
Spent the past day trying all sorts of workarounds to get basePath to work then found this PR, really strange that there is no update on this.
Yeah, I just hope the next-auth team takes a look at this. A lot of apps need a basePath. Our team actually tried different implementations without success. We are seriously thinking of removing next-auth all together due to this 🫤. We like this library, but a basePath is mandatory for us.
I understand the issue, thanks for putting together a thoughtful PR @k3k8 and everyone for flagging this. I actually ran into this issue a few months ago when I build the example to use next-auth deployed behind a proxy. At that time my workaround is basically appending the base path like this:
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "auth";
import { NextRequest } from "next/server";
const reqWithBasePath = (req: NextRequest, basePath = "/authjs") => {
const baseUrl = new URL(
(req.headers.get("x-forwarded-proto") ?? "https") +
"://" +
(req.headers.get("x-forwarded-host") ?? req.nextUrl.host)
);
const urlWithBasePath = new URL(
`${basePath}${req.nextUrl.pathname}${req.nextUrl.search}`,
baseUrl
);
return new NextRequest(urlWithBasePath.toString(), req);
};
export const GET = (req: NextRequest) => {
const nextReq = reqWithBasePath(req);
return handlers.GET(nextReq);
};
export const POST = (req: NextRequest) => {
const nextReq = reqWithBasePath(req);
return handlers.POST(nextReq);
};
https://github.com/ThangHuuVu/next-auth-behind-proxy/blob/cde4368b46fbda57157d62eaf1b93b258f5fadde/app/api/auth/%5B...nextauth%5D/route.ts
I'm a bit hesitant to merge this PR, though. I would actually prefer an official next.js API to get the basePath
value (this isn't exist yet AFAIK) instead of calling the estimateBasePath
function. The reason is that functions like estimateBasePath
is quite complex and could easily introduce bugs, we need at least some tests to make sure it won't break any existing behaviors.
I understand the complexity of the estimateBasePath function. I created it while testing with the following test cases. These tests were created based on packages/core/test/url-parsing.test.ts. Adding these test cases would ensure that future changes don't break existing behavior.
function estimateBasePath(configPathName: string, requestPathname: string): string | undefined {
const configSegments = configPathName.slice(1).split("/")
const requestSegments = requestPathname.slice(1).split("/")
if (configSegments[0] === requestSegments[0]) return undefined
let i = 0
while (i < configSegments.length && i < requestSegments.length && configSegments[i] !== requestSegments[0]) {
i++
}
return "/" + configSegments.slice(0, i).join("/")
}
const testCases = [
{ configPathName: '/base_path/api/auth', requestPathname: '/api/auth/session', expected: '/base_path' },
{ configPathName: '/api/auth', requestPathname: '/api/auth/session', expected: '' },
{ configPathName: '/test_path/api/auth', requestPathname: '/api/auth/signin/google', expected: '/test_path' },
{ configPathName: '/api/auth', requestPathname: '/api/auth/signin/google', expected: '' },
{ configPathName: '/test_path/test2/api/auth', requestPathname: '/api/auth/signout', expected: '/test_path/test2' },
{ configPathName: '/auth', requestPathname: '/auth/signin', expected: '' },
{ configPathName: '/sso/auth', requestPathname: '/auth/signin', expected: '/sso' },
];
testCases.forEach(({ configPathName, requestPathname, expected }) => {
const result = estimateBasePath(configPathName, requestPathname);
console.log(`Config: ${configPathName}, Request: ${requestPathname}, Expected: ${expected}, Result: ${result}`);
console.assert(result === expected, 'Test case failed');
});
The solution of adding the base path in route.ts, as @ThangHuuVu's suggested, works well in my environment too and seems like a good approach.
Given the numerous comments on this issue, I'd like to implement one of the following:
- Add information about the base path issue and @ThangHuuVu's code to the documentation
- Add these test cases to the next-auth package to test estimateBasePath
I'm planning to commit to either of these options.
I'd appreciate your thoughts on these proposals. I want to contribute in the way that best suits the project.
Any update on getting this fix in? I'm hitting it as well... setting a nextjs base path breaks the oauth redirect_uri, or if you set the nextjs base path and set a authjs base path to match, then you get Unknown Action (because the pathname doesn't match the base path). I can't find any way around this...
Any update on getting this fix in? I'm hitting it as well... setting a nextjs base path breaks the oauth redirect_uri, or if you set the nextjs base path and set a authjs base path to match, then you get Unknown Action (because the pathname doesn't match the base path). I can't find any way around this...
I found one way around this by remapping my routes in my route.ts file. That fixes things on the server, however, now I'm hitting issues on the client. I can't seem to get the client to call the apis with the correct path. I consistently get 404 errors when I have a base path (ie. not the default localhost:300/api/auth, but I have localhost:4000/app/api/auth). It never calls the correct api urls, just the default.
Can you please try the solution from ThangHuuVu's most recent reply? If you want to resolve the issue sooner, this would be the most effective approach.
Alternatively, you can test my code using the following method:
git clone -b fix-base-path https://github.com/k3k8/next-auth.git
cd next-auth
corepack enable pnpm
pnpm install
pnpm build
cd packages/next-auth/
pnpm pack
This will generate a package file named something like next-auth-5.0.0-beta.20.tgz
.
Move this file to your project directory and install it by updating your package.json
:
{
"dependencies": {
"next-auth": "./next-auth-5.0.0-beta.20.tgz"
}
}
Then run pnpm install
to update your dependencies.